@animalabs/agent-framework 0.24.0 → 0.25.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/dist/src/agent.d.ts +5 -0
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +27 -8
- package/dist/src/agent.js.map +1 -1
- package/dist/src/clock-rows.d.ts +115 -0
- package/dist/src/clock-rows.d.ts.map +1 -0
- package/dist/src/clock-rows.js +222 -0
- package/dist/src/clock-rows.js.map +1 -0
- package/dist/src/framework.d.ts +98 -6
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +514 -80
- package/dist/src/framework.js.map +1 -1
- package/dist/src/index.d.ts +2 -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/mcpl/channel-registry.d.ts +9 -0
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +49 -6
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/protocol-family.js +1 -1
- package/dist/src/mcpl/references.d.ts +4 -1
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +5 -2
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +9 -1
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +118 -28
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +6 -2
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/text-as-stored.d.ts +31 -0
- package/dist/src/text-as-stored.d.ts.map +1 -0
- package/dist/src/text-as-stored.js +70 -0
- package/dist/src/text-as-stored.js.map +1 -0
- package/dist/src/timezone.d.ts +4 -2
- package/dist/src/timezone.d.ts.map +1 -1
- package/dist/src/timezone.js +4 -2
- package/dist/src/timezone.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts +46 -7
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +60 -7
- package/dist/src/tool-image-ledger.js.map +1 -1
- package/dist/src/tool-presentation.d.ts.map +1 -1
- package/dist/src/tool-presentation.js +9 -1
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tool-result-guard.d.ts +10 -8
- package/dist/src/tool-result-guard.d.ts.map +1 -1
- package/dist/src/tool-result-guard.js +12 -10
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tool-result-history.d.ts +17 -6
- package/dist/src/tool-result-history.d.ts.map +1 -1
- package/dist/src/tool-result-history.js +35 -10
- package/dist/src/tool-result-history.js.map +1 -1
- package/dist/src/tune-out/coordinator.js +3 -3
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/types/agent.d.ts +27 -0
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/events.d.ts +13 -3
- package/dist/src/types/events.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +7 -4
- package/dist/src/types/framework.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agent.ts +27 -8
- package/src/clock-rows.ts +302 -0
- package/src/framework.ts +538 -85
- package/src/index.ts +2 -0
- package/src/mcpl/channel-registry.ts +54 -8
- package/src/mcpl/protocol-family.ts +1 -1
- package/src/mcpl/references.ts +5 -2
- package/src/modules/workspace/index.ts +121 -29
- package/src/modules/workspace/types.ts +6 -2
- package/src/text-as-stored.ts +73 -0
- package/src/timezone.ts +4 -2
- package/src/tool-image-ledger.ts +62 -8
- package/src/tool-presentation.ts +8 -1
- package/src/tool-result-guard.ts +12 -10
- package/src/tool-result-history.ts +35 -9
- package/src/tune-out/coordinator.ts +3 -3
- package/src/types/agent.ts +28 -0
- package/src/types/events.ts +13 -3
- package/src/types/framework.ts +7 -4
package/src/tool-presentation.ts
CHANGED
|
@@ -85,7 +85,8 @@ function quotedList(names: string[], conjunction: 'and' | 'or' = 'and', quote: (
|
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* Every problem that keeps a document from being used, each by where it is;
|
|
88
|
-
* none for a usable one.
|
|
88
|
+
* none for a usable one. An object with no keys never reaches this:
|
|
89
|
+
* parseDocument() reads it as the empty document first. A defaults file (`descriptionsOnly`) may set
|
|
89
90
|
* descriptions only, since visibility is the resident's to choose.
|
|
90
91
|
*/
|
|
91
92
|
function documentProblems(doc: unknown, descriptionsOnly: boolean): string[] {
|
|
@@ -125,6 +126,12 @@ function parseDocument(raw: string, descriptionsOnly: boolean): PresentationDocu
|
|
|
125
126
|
let doc: unknown;
|
|
126
127
|
try { doc = JSON.parse(raw); }
|
|
127
128
|
catch (error) { throw new PresentationFileError([`it isn't valid JSON: ${errorMessage(error)}`]); }
|
|
129
|
+
// An object with no keys carries no settings, so it's the empty document,
|
|
130
|
+
// as a missing resident file is: it's what an operator writes by hand to
|
|
131
|
+
// start one. A missing defaults file is still reported, by read().
|
|
132
|
+
// A 0-byte file isn't JSON and still refuses, since the store's
|
|
133
|
+
// temp-and-rename never writes one: that's what a lost file looks like.
|
|
134
|
+
if (doc && typeof doc === 'object' && !Array.isArray(doc) && Object.keys(doc).length === 0) return { version: 1, tools: {} };
|
|
128
135
|
const problems = documentProblems(doc, descriptionsOnly);
|
|
129
136
|
if (problems.length) throw new PresentationFileError(problems);
|
|
130
137
|
return doc as PresentationDocument;
|
package/src/tool-result-guard.ts
CHANGED
|
@@ -86,10 +86,11 @@ export function withheldResultNotice(spill: WithheldSpill, auditDurable: boolean
|
|
|
86
86
|
/**
|
|
87
87
|
* What a withheld result's stub keeps of it: the fields of its data that its
|
|
88
88
|
* tool marked as saying what it acted on (ToolResult.keepWhenWithheld), in
|
|
89
|
-
* the order marked, serialized as a stored result is. Past the
|
|
90
|
-
* they're cut, with a note that
|
|
91
|
-
* a later field can then be cut
|
|
92
|
-
* nothing, as for a failed result
|
|
89
|
+
* the order marked, serialized as a stored result is. Past `maxChars`, the
|
|
90
|
+
* inline cap or less (storeResults' keptCap), they're cut, with a note that
|
|
91
|
+
* says so and where the whole result is, since a later field can then be cut
|
|
92
|
+
* away entirely. Undefined when it keeps nothing, as for a failed result
|
|
93
|
+
* (agent-framework #277). A write's file
|
|
93
94
|
* list is evidence of what it did, rarely what drew a refusal, and keeping it
|
|
94
95
|
* is what shows a clobbered file when the receipt is withheld.
|
|
95
96
|
*/
|
|
@@ -109,7 +110,7 @@ export function keptWhenWithheld(result: CompletedToolCall['result'], maxChars:
|
|
|
109
110
|
// Never half a surrogate pair: the stub goes to the provider as text, so
|
|
110
111
|
// the cut can be one character short of the cap.
|
|
111
112
|
const head = safeSlice(whole, 0, maxChars);
|
|
112
|
-
return `${head} [cut
|
|
113
|
+
return `${head} [cut: ${head.length} of these fields' ${whole.length} ` +
|
|
113
114
|
'characters; the whole result is where this note says]';
|
|
114
115
|
}
|
|
115
116
|
|
|
@@ -313,15 +314,16 @@ export class ToolResultGuard {
|
|
|
313
314
|
: JSON.parse(json);
|
|
314
315
|
}
|
|
315
316
|
|
|
316
|
-
/** `
|
|
317
|
-
*
|
|
317
|
+
/** `keptCap` is where what a stub keeps of a withheld result is cut: the
|
|
318
|
+
* cap the caller stored `content` under, less the stub's notice room where
|
|
319
|
+
* its message's bound sets it (toolResultShare, tool-image-ledger.ts). */
|
|
318
320
|
storeResults(
|
|
319
321
|
content: ContentBlock[],
|
|
320
322
|
wireResults: ToolResult[],
|
|
321
323
|
originals: CompletedToolCall[],
|
|
322
324
|
metadata: MessageMetadata | undefined,
|
|
323
|
-
/**
|
|
324
|
-
|
|
325
|
+
/** Where what a stub keeps is cut. */
|
|
326
|
+
keptCap: number | undefined,
|
|
325
327
|
): MessageId {
|
|
326
328
|
if (!this.enabled) return this.cm.addMessage('user', content, metadata);
|
|
327
329
|
if (this.pending) {
|
|
@@ -355,7 +357,7 @@ export class ToolResultGuard {
|
|
|
355
357
|
const kept = new Map<number, string>();
|
|
356
358
|
for (const [index, block] of content.entries()) {
|
|
357
359
|
const result = block.type === 'tool_result' ? results.get(block.toolUseId) : undefined;
|
|
358
|
-
const text = result ? keptWhenWithheld(result,
|
|
360
|
+
const text = result ? keptWhenWithheld(result, keptCap) : undefined;
|
|
359
361
|
if (text !== undefined) kept.set(index, text);
|
|
360
362
|
}
|
|
361
363
|
// Compression hold (CM >= 0.12): placed BEFORE onNewMessage fires, so no
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
* `maxChars` slice and re-introduces, one turn deferred, exactly the silent
|
|
10
10
|
* hallucination this feature exists to kill.
|
|
11
11
|
*
|
|
12
|
-
* For MCP-shaped content arrays, this helper keeps text blocks verbatim
|
|
12
|
+
* For MCP-shaped content arrays, this helper keeps text blocks verbatim,
|
|
13
|
+
* apart from a lone UTF-16 surrogate, which becomes U+FFFD (#695), and
|
|
13
14
|
* replaces image blocks with a short `[image: mimeType, ~NKB]` placeholder.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* sits in the data, so its uri
|
|
17
|
-
* claims a uri or a disposition
|
|
15
|
+
* A string is already text and is kept as it is. For anything else it falls
|
|
16
|
+
* back to JSON, written by RFC-005's rules at every depth (historyJson): a
|
|
17
|
+
* reference is shown as its stub wherever it sits in the data, so its uri
|
|
18
|
+
* never reaches history, and inline data that claims a uri or a disposition
|
|
19
|
+
* is withheld.
|
|
18
20
|
*/
|
|
19
21
|
|
|
20
22
|
import { INLINE_WITHHELD_TEXT, isInlineContradiction, referenceStubOrNull } from './mcpl/references.js';
|
|
@@ -72,9 +74,19 @@ export function toolResultDataToHistoryString(
|
|
|
72
74
|
options?: HistorySerializeOptions,
|
|
73
75
|
): string {
|
|
74
76
|
const fromArray = tryHistoryStringFromContentArray(data, options);
|
|
77
|
+
// A string is the text the tool means to say, kept as it is: JSON would
|
|
78
|
+
// quote it and escape every newline, so a multi-line result would reach
|
|
79
|
+
// the model, a script and its spill file as one escaped line. Kept well
|
|
80
|
+
// formed: a lone UTF-16 surrogate, which JSON escaped, isn't text, a
|
|
81
|
+
// provider can refuse a request that carries one, and UTF-8 can't store
|
|
82
|
+
// it in a spill file, so it reads as U+FFFD.
|
|
75
83
|
// JSON leaves out undefined, a function and a symbol: a result with no data
|
|
76
84
|
// is stored as no text, as an empty content array is.
|
|
77
|
-
|
|
85
|
+
// RFC-005 shows only objects otherwise (shownInHistory), so a string has
|
|
86
|
+
// nothing for historyJson to stub or withhold.
|
|
87
|
+
const str = fromArray
|
|
88
|
+
?? (typeof data === 'string' ? data.toWellFormed() : historyJson(data))
|
|
89
|
+
?? '';
|
|
78
90
|
return maxChars ? truncateForHistory(str, maxChars) : str;
|
|
79
91
|
}
|
|
80
92
|
|
|
@@ -86,6 +98,14 @@ export function toolResultDataToHistoryString(
|
|
|
86
98
|
*
|
|
87
99
|
* Each field is read once: the direct Agent API passes its caller's own
|
|
88
100
|
* result here, unchecked, and a getter needn't answer the same way twice.
|
|
101
|
+
*
|
|
102
|
+
* Each text a tool result's copies carry is made well formed where it's
|
|
103
|
+
* taken from the result: a lone UTF-16 surrogate becomes U+FFFD, as the
|
|
104
|
+
* context store makes it when it writes, so the copy sent to membrane and the
|
|
105
|
+
* copy stored carry the same text (#695). Other data goes through JSON,
|
|
106
|
+
* which escapes a lone half. toWellFormed() keeps the length,
|
|
107
|
+
* so the cut, the spans and the slots are computed from the well-formed text
|
|
108
|
+
* as before; a cut at a lone high half no longer steps back over it.
|
|
89
109
|
*/
|
|
90
110
|
export function nonErrorResultText(
|
|
91
111
|
result: { data?: unknown; error?: string },
|
|
@@ -95,13 +115,17 @@ export function nonErrorResultText(
|
|
|
95
115
|
const data = result.data;
|
|
96
116
|
if (data === undefined || data === null) {
|
|
97
117
|
const error = result.error;
|
|
98
|
-
if (typeof error === 'string' && error)
|
|
118
|
+
if (typeof error === 'string' && error) {
|
|
119
|
+
const text = error.toWellFormed();
|
|
120
|
+
return maxChars ? truncateForHistory(text, maxChars) : text;
|
|
121
|
+
}
|
|
99
122
|
}
|
|
100
123
|
return toolResultDataToHistoryString(data, maxChars, options);
|
|
101
124
|
}
|
|
102
125
|
|
|
103
126
|
/** Bounded copy of an arbitrary string with the standard truncation notice,
|
|
104
|
-
* cut at the cap
|
|
127
|
+
* cut at the cap where a tool result's stored copy is cut at that cap
|
|
128
|
+
* (toolResultCutAt), so it
|
|
105
129
|
* never ends in part of a slot. Each of its callers' readers sees this
|
|
106
130
|
* very text, never the images behind a placeholder, so nothing past the cut
|
|
107
131
|
* is kept for save_recent_image's index (#403). The framework stores a tool
|
|
@@ -127,7 +151,9 @@ function tryHistoryStringFromContentArray(data: unknown, options?: HistorySerial
|
|
|
127
151
|
if (b.type === 'text') {
|
|
128
152
|
// A text that isn't a string shows as the converters' placeholder,
|
|
129
153
|
// rather than sending the whole array to the JSON fallback.
|
|
130
|
-
|
|
154
|
+
// Read once: a direct Agent API result is unchecked.
|
|
155
|
+
const text = b.text;
|
|
156
|
+
parts.push({ kind: 'text', text: typeof text === 'string' ? text.toWellFormed() : NON_STRING_TEXT_PLACEHOLDER });
|
|
131
157
|
} else if (isInlineContradiction(b)) {
|
|
132
158
|
// RFC-005 vector 2: checked before the data branch so inline data
|
|
133
159
|
// claiming bulk disposition is withheld, not rendered.
|
|
@@ -290,9 +290,9 @@ export class TuneOutCoordinator {
|
|
|
290
290
|
startedAtSequence: this.hooks.currentSequence(),
|
|
291
291
|
...(expiresAtMs !== undefined ? { expiresAtMs } : {}),
|
|
292
292
|
};
|
|
293
|
-
// A tune-out
|
|
294
|
-
//
|
|
295
|
-
//
|
|
293
|
+
// A tune-out that ended in the registry without this coordinator's
|
|
294
|
+
// cancel would have left its timers armed: clear them, so none of them
|
|
295
|
+
// acts on this one.
|
|
296
296
|
this.clearTimers(serverId, channelId);
|
|
297
297
|
this.channelRegistry.enterTuneOut(serverId, channelId, params, source);
|
|
298
298
|
this.armExpiry(serverId, channelId, params);
|
package/src/types/agent.ts
CHANGED
|
@@ -218,6 +218,34 @@ export interface AgentConfig {
|
|
|
218
218
|
* content-free system receipt is stored instead. Default false.
|
|
219
219
|
*/
|
|
220
220
|
toolWrapperProseGuard?: boolean;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Clock rows (off when left out): when a boundary of this interval on the
|
|
224
|
+
* agent's local wall clock (counted from local midnight, in the
|
|
225
|
+
* framework's `timeZone`) has passed since the last clock row, the window
|
|
226
|
+
* takes one more — `Clock: 10:50 Europe/Berlin (UTC+02:00)`, with the date
|
|
227
|
+
* when the day has changed — before the next thing it takes: an incoming
|
|
228
|
+
* event, a turn's start, or, mid-turn, after a tool round's results, where
|
|
229
|
+
* it also joins the live stream. Only the latest boundary is written, and
|
|
230
|
+
* a window that holds no clock row yet takes one at the next of these. A
|
|
231
|
+
* clock reads the boundary's time where its tick would have stood, else
|
|
232
|
+
* the time it is written (after a tool's results, say), so it never
|
|
233
|
+
* reads earlier than a row before it. A clock wakes no one. See
|
|
234
|
+
* src/clock-rows.ts.
|
|
235
|
+
*
|
|
236
|
+
* - A stream that can't carry injected messages (membrane's XML prefill
|
|
237
|
+
* path) takes no mid-turn clock: the next event or turn start writes it.
|
|
238
|
+
* - On some models a clock after a tool's results drops the loop's
|
|
239
|
+
* earlier thinking from the request, as any message heard while acting
|
|
240
|
+
* does there: at most once an interval.
|
|
241
|
+
* - Residents share one window, so every resident reads the clock rows any
|
|
242
|
+
* of them writes, a resident with no `clock` of its own included. Each
|
|
243
|
+
* writes one when a boundary of its own interval has passed since the
|
|
244
|
+
* window's last clock, whichever resident wrote that. The subconscious
|
|
245
|
+
* takes none of its own: it reads the residents' through its merged
|
|
246
|
+
* view.
|
|
247
|
+
*/
|
|
248
|
+
clock?: import('../clock-rows.js').ClockConfig;
|
|
221
249
|
}
|
|
222
250
|
|
|
223
251
|
/** The intentionally small set of agent settings that may change in-process. */
|
package/src/types/events.ts
CHANGED
|
@@ -186,7 +186,13 @@ export interface ModuleEvent {
|
|
|
186
186
|
export interface ToolResult {
|
|
187
187
|
/** Whether the tool succeeded */
|
|
188
188
|
success: boolean;
|
|
189
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* Result data (if success). The model reads a string as that text, an MCP
|
|
191
|
+
* content array whose every block the framework knows as those blocks, and
|
|
192
|
+
* anything else as JSON, where RFC-005 shows a reference as its stub and
|
|
193
|
+
* withholds inline data that claims a uri or a disposition. With no data,
|
|
194
|
+
* it reads the result's error text, if any.
|
|
195
|
+
*/
|
|
190
196
|
data?: unknown;
|
|
191
197
|
/** Error message (if failure) */
|
|
192
198
|
error?: string;
|
|
@@ -270,9 +276,13 @@ export interface ToolCall {
|
|
|
270
276
|
/**
|
|
271
277
|
* How long this call's result may be and still reach its caller whole,
|
|
272
278
|
* measured as the framework stores it: an error's text, or a success's data
|
|
273
|
-
* as JSON (a content array as its text). For a model's call it is the
|
|
279
|
+
* as JSON (a string or a content array as its text). For a model's call it is the
|
|
274
280
|
* agent's inline cap, past which the framework spills the result to a
|
|
275
|
-
* workspace file and sends a preview.
|
|
281
|
+
* workspace file and sends a preview. A result that long is stored whole
|
|
282
|
+
* unless its round's results together pass the readers' per-message bound,
|
|
283
|
+
* or a save_recent_image in the round counted it by its share: then each
|
|
284
|
+
* result of the round takes an equal share of that bound, and one past its
|
|
285
|
+
* share spills. For a call from a running
|
|
276
286
|
* code_execution script it is the longest result that reaches the script
|
|
277
287
|
* whole whatever its characters, from the script's memory: the script
|
|
278
288
|
* takes an answer of up to a third of its memoryMaxBytes, in which a
|
package/src/types/framework.ts
CHANGED
|
@@ -117,9 +117,10 @@ export interface FrameworkConfig {
|
|
|
117
117
|
subconscious?: import('../tune-out/tools.js').SubconsciousConfig;
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
|
-
* IANA zone
|
|
121
|
-
*
|
|
122
|
-
*
|
|
120
|
+
* IANA zone of the agent's wall clock: it renders wall-clock times for the
|
|
121
|
+
* agent, and it sets where clock rows' boundaries fall (AgentConfig.clock,
|
|
122
|
+
* counted from local midnight here). Stored/protocol timestamps remain
|
|
123
|
+
* epoch/UTC. Defaults to AGENT_TIMEZONE, then the process zone.
|
|
123
124
|
*/
|
|
124
125
|
timeZone?: string;
|
|
125
126
|
|
|
@@ -247,7 +248,9 @@ export interface FrameworkConfig {
|
|
|
247
248
|
* takes precedence over this for that agent; the EFFECTIVE cap for every
|
|
248
249
|
* source is min(desired, strategy bound) — desired values above the
|
|
249
250
|
* strategy-derived bound (maxMessageTokens * 4) are hard-clamped to it,
|
|
250
|
-
* with the clamp reported via agent_settings get.
|
|
251
|
+
* with the clamp reported via agent_settings get. For an ordinary resident
|
|
252
|
+
* the bound is the smallest among the agents that read the shared slot,
|
|
253
|
+
* reported as clamped by `reader-bound` when another reader's sets it.
|
|
251
254
|
*/
|
|
252
255
|
toolResultInlineMaxChars?: number;
|
|
253
256
|
|