@animalabs/agent-framework 0.28.0 → 0.29.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 +88 -25
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +4 -1
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts.map +1 -1
- package/dist/src/api/mcp-server-core.js +3 -12
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/api/server.d.ts.map +1 -1
- package/dist/src/api/server.js +7 -3
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/auto-continue.d.ts +2 -0
- package/dist/src/auto-continue.d.ts.map +1 -1
- package/dist/src/auto-continue.js +5 -2
- package/dist/src/auto-continue.js.map +1 -1
- package/dist/src/code-execution/isolate/runner.d.ts +7 -5
- package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/runner.js +7 -5
- package/dist/src/code-execution/isolate/runner.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +3 -2
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/framework.d.ts +164 -36
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +1120 -479
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +34 -10
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +118 -48
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/index.d.ts +1 -1
- package/dist/src/gate/index.d.ts.map +1 -1
- package/dist/src/gate/index.js +1 -1
- package/dist/src/gate/index.js.map +1 -1
- package/dist/src/gate/types.d.ts +12 -2
- package/dist/src/gate/types.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.d.ts +18 -0
- package/dist/src/heartbeat/heartbeat.d.ts.map +1 -1
- package/dist/src/heartbeat/heartbeat.js +34 -8
- package/dist/src/heartbeat/heartbeat.js.map +1 -1
- package/dist/src/held-writes.d.ts +3 -3
- package/dist/src/held-writes.js +3 -3
- package/dist/src/host-presentation.d.ts +170 -0
- package/dist/src/host-presentation.d.ts.map +1 -0
- package/dist/src/host-presentation.js +561 -0
- package/dist/src/host-presentation.js.map +1 -0
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/line-reader.d.ts +4 -0
- package/dist/src/line-reader.d.ts.map +1 -0
- package/dist/src/line-reader.js +59 -0
- package/dist/src/line-reader.js.map +1 -0
- package/dist/src/mcpl/channel-registry.d.ts +34 -6
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +131 -53
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
- package/dist/src/mcpl/conversation-router.js +2 -1
- package/dist/src/mcpl/conversation-router.js.map +1 -1
- package/dist/src/mcpl/convert-block.d.ts +1 -0
- package/dist/src/mcpl/convert-block.d.ts.map +1 -1
- package/dist/src/mcpl/convert-block.js +16 -4
- package/dist/src/mcpl/convert-block.js.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
- package/dist/src/mcpl/hook-orchestrator.js +4 -3
- package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +3 -1
- package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
- package/dist/src/mcpl/inbound-source.js +3 -1
- package/dist/src/mcpl/inbound-source.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +11 -7
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +3 -2
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +25 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-handler.js +2 -2
- package/dist/src/mcpl/push-handler.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +12 -40
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +29 -23
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +20 -20
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
- package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
- package/dist/src/mcpl/skip-reply-wake.js +25 -0
- package/dist/src/mcpl/skip-reply-wake.js.map +1 -1
- package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
- package/dist/src/mcpl/tool-presence.js +18 -17
- package/dist/src/mcpl/tool-presence.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +17 -16
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/toolset-host.d.ts +8 -26
- package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
- package/dist/src/mcpl/toolset-host.js +16 -6
- package/dist/src/mcpl/toolset-host.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts +14 -25
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +180 -50
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +1 -1
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +16 -16
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +28 -12
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/module-registry.d.ts +6 -7
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +6 -7
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/api/index.d.ts.map +1 -1
- package/dist/src/modules/api/index.js +4 -2
- package/dist/src/modules/api/index.js.map +1 -1
- package/dist/src/modules/discord/index.d.ts.map +1 -1
- package/dist/src/modules/discord/index.js +33 -25
- package/dist/src/modules/discord/index.js.map +1 -1
- package/dist/src/modules/history/index.js +8 -6
- package/dist/src/modules/history/index.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +7 -0
- package/dist/src/modules/sender-name.d.ts.map +1 -0
- package/dist/src/modules/sender-name.js +10 -0
- package/dist/src/modules/sender-name.js.map +1 -0
- package/dist/src/modules/workspace/answers.d.ts +5 -1
- package/dist/src/modules/workspace/answers.d.ts.map +1 -1
- package/dist/src/modules/workspace/answers.js +16 -1
- package/dist/src/modules/workspace/answers.js.map +1 -1
- package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
- package/dist/src/modules/workspace/direct-write.js +6 -4
- package/dist/src/modules/workspace/direct-write.js.map +1 -1
- package/dist/src/modules/workspace/effects.d.ts +4 -2
- package/dist/src/modules/workspace/effects.d.ts.map +1 -1
- package/dist/src/modules/workspace/effects.js +50 -40
- package/dist/src/modules/workspace/effects.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +1 -0
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +66 -36
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +51 -20
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +120 -82
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +7 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +133 -37
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/notice-tags.d.ts +79 -0
- package/dist/src/notice-tags.d.ts.map +1 -0
- package/dist/src/notice-tags.js +83 -0
- package/dist/src/notice-tags.js.map +1 -0
- package/dist/src/reasoning-loss-notice.d.ts.map +1 -1
- package/dist/src/reasoning-loss-notice.js +5 -3
- package/dist/src/reasoning-loss-notice.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +1 -53
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +56 -3
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/supplied-text.d.ts +35 -0
- package/dist/src/supplied-text.d.ts.map +1 -0
- package/dist/src/supplied-text.js +141 -0
- package/dist/src/supplied-text.js.map +1 -0
- package/dist/src/tool-args.d.ts.map +1 -1
- package/dist/src/tool-args.js +9 -8
- package/dist/src/tool-args.js.map +1 -1
- package/dist/src/tool-image-ledger.d.ts.map +1 -1
- package/dist/src/tool-image-ledger.js +2 -1
- 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 +5 -4
- package/dist/src/tool-presentation.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +3 -2
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +24 -26
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/types/agent.d.ts +23 -9
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +43 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +22 -11
- package/dist/src/types/module.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/agent.ts +4 -1
- package/src/api/mcp-server-core.ts +3 -14
- package/src/api/server.ts +7 -3
- package/src/auto-continue.ts +8 -2
- package/src/code-execution/isolate/runner.ts +7 -5
- package/src/code-execution/isolate/scripts.ts +3 -2
- package/src/framework.ts +1132 -492
- package/src/gate/event-gate.ts +128 -47
- package/src/gate/index.ts +1 -1
- package/src/gate/types.ts +12 -2
- package/src/heartbeat/heartbeat.ts +48 -9
- package/src/held-writes.ts +3 -3
- package/src/host-presentation.ts +605 -0
- package/src/index.ts +2 -2
- package/src/line-reader.ts +58 -0
- package/src/mcpl/channel-registry.ts +137 -52
- package/src/mcpl/conversation-router.ts +2 -1
- package/src/mcpl/convert-block.ts +17 -4
- package/src/mcpl/hook-orchestrator.ts +4 -3
- package/src/mcpl/inbound-source.ts +3 -1
- package/src/mcpl/modern-connection.ts +11 -7
- package/src/mcpl/protocol-family.ts +24 -3
- package/src/mcpl/push-handler.ts +2 -2
- package/src/mcpl/references.ts +31 -23
- package/src/mcpl/server-connection.ts +20 -18
- package/src/mcpl/skip-reply-wake.ts +35 -0
- package/src/mcpl/tool-presence.ts +18 -17
- package/src/mcpl/tool-signature.ts +17 -16
- package/src/mcpl/toolset-host.ts +18 -6
- package/src/mcpl/toolsets-module.ts +142 -50
- package/src/mcpl/transport.ts +18 -14
- package/src/mcpl/types.ts +29 -12
- package/src/module-registry.ts +6 -7
- package/src/modules/api/index.ts +4 -2
- package/src/modules/discord/index.ts +37 -25
- package/src/modules/history/index.ts +8 -6
- package/src/modules/sender-name.ts +9 -0
- package/src/modules/workspace/answers.ts +20 -2
- package/src/modules/workspace/direct-write.ts +4 -3
- package/src/modules/workspace/effects.ts +50 -40
- package/src/modules/workspace/index.ts +58 -34
- package/src/modules/workspace/observe.ts +139 -88
- package/src/modules/workspace/reconcile.ts +159 -36
- package/src/notice-tags.ts +84 -0
- package/src/reasoning-loss-notice.ts +5 -3
- package/src/silent-heartbeat.ts +4 -3
- package/src/supplied-text.ts +154 -0
- package/src/tool-args.ts +9 -8
- package/src/tool-image-ledger.ts +2 -1
- package/src/tool-presentation.ts +5 -4
- package/src/tune-out/coordinator.ts +25 -27
- package/src/types/agent.ts +23 -9
- package/src/types/framework.ts +44 -0
- package/src/types/module.ts +23 -11
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text the framework didn't write (what a participant or a server supplied),
|
|
3
|
+
* put beside structure of the framework's own: the reply judge's document,
|
|
4
|
+
* and notices the framework writes. A value that can end its line can start
|
|
5
|
+
* a line that reads as the framework's own.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CONTEXT_MANAGER_TAGS, MEMBRANE_TAGS, NOTICE_TAG } from './notice-tags.js';
|
|
9
|
+
|
|
10
|
+
/** Every form of line break a reader might take for one: CR LF, CR, LF,
|
|
11
|
+
* vertical tab, form feed, NEL, and the Unicode line and paragraph separators. */
|
|
12
|
+
const LINE_BREAK = /\r\n|[\r\n\u000b\u000c\u0085\u2028\u2029]/g;
|
|
13
|
+
|
|
14
|
+
/** Text with each line break, in whatever form, as `\n`. */
|
|
15
|
+
export function normalizeLineBreaks(text: string): string {
|
|
16
|
+
return text.replace(LINE_BREAK, '\n');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A value kept to one line: each run of line breaks, in whatever form, as one
|
|
20
|
+
* space, so it can't end the line it's put in or start one of its own. */
|
|
21
|
+
export function oneLine(text: string): string {
|
|
22
|
+
return normalizeLineBreaks(text).replace(/\n+/g, ' ');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const INVISIBLE = '\\p{Cf}\\p{Default_Ignorable_Code_Point}';
|
|
26
|
+
const SKIPPED = `[\\s\\p{Cc}${INVISIBLE}]`;
|
|
27
|
+
const tagWord = (word: string): string =>
|
|
28
|
+
[...word].map((c) => (/[\\^$.*+?()[\]{}|/]/.test(c) ? `\\${c}` : c)).join(`${SKIPPED}*`);
|
|
29
|
+
const NOTICE_OPENING = new RegExp(
|
|
30
|
+
`\\[(?=${SKIPPED}*(?:${[...Object.values(NOTICE_TAG), ...CONTEXT_MANAGER_TAGS, ...MEMBRANE_TAGS].map((tag) => tag.split(' ').map(tagWord).join(`${SKIPPED}+`)).join('|')})(?![\\p{L}\\p{N}]))`,
|
|
31
|
+
'giu',
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Texts read as one string, joined by line breaks, with a backslash put
|
|
36
|
+
* before the bracket of each opening of a listed tag (NOTICE_TAG,
|
|
37
|
+
* CONTEXT_MANAGER_TAGS, MEMBRANE_TAGS) that hasn't one before it in its own
|
|
38
|
+
* text. A text `marks` says false of is read and never changed.
|
|
39
|
+
*
|
|
40
|
+
* Every opening, not only one that starts a line: membrane's XML formatter
|
|
41
|
+
* writes `participant: ` before each message, so the framework's own
|
|
42
|
+
* openings stand mid-line there too. It joins a message's text blocks with
|
|
43
|
+
* line breaks and sets its images apart from that text, while the native
|
|
44
|
+
* formatter sends each block on its own, so an opening split across a
|
|
45
|
+
* message's text blocks is one either way. In any case, by Unicode's case folding, and
|
|
46
|
+
* through whitespace, control and invisible characters anywhere inside the
|
|
47
|
+
* bracket, which a model reads past.
|
|
48
|
+
*/
|
|
49
|
+
export function markedTexts(texts: readonly string[], marks: readonly boolean[] = []): string[] {
|
|
50
|
+
const starts: number[] = [];
|
|
51
|
+
let at = 0;
|
|
52
|
+
for (const text of texts) {
|
|
53
|
+
starts.push(at);
|
|
54
|
+
at += text.length + 1;
|
|
55
|
+
}
|
|
56
|
+
const offsets: number[][] = texts.map(() => []);
|
|
57
|
+
for (const match of texts.join('\n').matchAll(NOTICE_OPENING)) {
|
|
58
|
+
let holder = texts.length - 1;
|
|
59
|
+
while (starts[holder]! > match.index) holder--;
|
|
60
|
+
const offset = match.index - starts[holder]!;
|
|
61
|
+
if (marks[holder] !== false && texts[holder]![offset - 1] !== '\\') offsets[holder]!.push(offset);
|
|
62
|
+
}
|
|
63
|
+
return texts.map((text, i) => withMarks(text, offsets[i]!));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function withMarks(text: string, offsets: readonly number[]): string {
|
|
67
|
+
let marked = '';
|
|
68
|
+
let from = 0;
|
|
69
|
+
for (const offset of offsets) {
|
|
70
|
+
marked += `${text.slice(from, offset)}\\`;
|
|
71
|
+
from = offset;
|
|
72
|
+
}
|
|
73
|
+
return marked + text.slice(from);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function closedWithin(text: string, at: number): boolean {
|
|
77
|
+
let depth = 0;
|
|
78
|
+
for (let i = at; i < text.length; i++) {
|
|
79
|
+
if (text[i] === '[') depth++;
|
|
80
|
+
else if (text[i] === ']' && --depth === 0) return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function unmarkedOpenings(text: string): number[] {
|
|
86
|
+
return [...text.matchAll(NOTICE_OPENING)].map((match) => match.index).filter((at) => text[at - 1] !== '\\');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function markedText(text: string): string {
|
|
90
|
+
return markedTexts([text])[0]!;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function markNoticeOpenings<T extends object>(blocks: readonly T[], marks: (index: number) => boolean = () => true): T[] {
|
|
94
|
+
const at: number[] = [];
|
|
95
|
+
blocks.forEach((block, index) => {
|
|
96
|
+
const { type, text } = (block ?? {}) as { type?: unknown; text?: unknown };
|
|
97
|
+
if (type === 'text' && typeof text === 'string') at.push(index);
|
|
98
|
+
});
|
|
99
|
+
const textOf = (index: number): string => (blocks[index] as unknown as { text: string }).text;
|
|
100
|
+
const marked = markedTexts(at.map(textOf), at.map(marks));
|
|
101
|
+
const out = [...blocks];
|
|
102
|
+
at.forEach((index, i) => {
|
|
103
|
+
if (marked[i] !== textOf(index)) out[index] = { ...blocks[index]!, text: marked[i] };
|
|
104
|
+
});
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function suppliedValue(value: unknown): string {
|
|
109
|
+
return markedText(oneLine(String(value)));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function cutToFit(text: string, length: number): string {
|
|
113
|
+
let end = Math.max(0, Math.floor(length));
|
|
114
|
+
if (end >= text.length) return text;
|
|
115
|
+
for (;;) {
|
|
116
|
+
const last = text.charCodeAt(end - 1);
|
|
117
|
+
if (end < text.length && last >= 0xd800 && last <= 0xdbff) end--;
|
|
118
|
+
else if (text[end - 1] === '\\' && text[end] === '[') end--;
|
|
119
|
+
else {
|
|
120
|
+
const cut = text.slice(0, end);
|
|
121
|
+
const open = unmarkedOpenings(cut).find((at) => !closedWithin(cut, at));
|
|
122
|
+
if (open === undefined) return cut;
|
|
123
|
+
end = open;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function showsAsItself(value: string): boolean {
|
|
129
|
+
return value !== '' && suppliedValue(value) === value && value.isWellFormed() && !EDGE_OR_QUOTE.test(value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const EDGE_OR_QUOTE = /^\s|\s$|["\\]/;
|
|
133
|
+
|
|
134
|
+
export function quotedValue(value: unknown, space?: number): string {
|
|
135
|
+
return (JSON.stringify(value, null, space) ?? 'null').replace(/"(?:[^"\\]|\\.)*"/g, (literal) => quotedString(JSON.parse(literal)));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function quotedString(text: string): string {
|
|
139
|
+
let quoted = '';
|
|
140
|
+
let from = 0;
|
|
141
|
+
for (const match of text.matchAll(NOTICE_OPENING)) {
|
|
142
|
+
quoted += `${jsonText(text.slice(from, match.index))}\\u005b`;
|
|
143
|
+
from = match.index + 1;
|
|
144
|
+
}
|
|
145
|
+
return `"${quoted}${jsonText(text.slice(from))}"`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// JSON.stringify leaves NEL, U+2028 and U+2029 as they are.
|
|
149
|
+
const jsonText = (text: string): string =>
|
|
150
|
+
JSON.stringify(text).slice(1, -1).replace(/[\u0085\u2028\u2029]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
151
|
+
|
|
152
|
+
export function nameToTypeBack(name: string): string {
|
|
153
|
+
return showsAsItself(name) ? name : quotedValue(name);
|
|
154
|
+
}
|
package/src/tool-args.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nameToTypeBack, quotedValue } from './supplied-text.js';
|
|
1
2
|
import type { ToolDefinition } from './types/index.js';
|
|
2
3
|
|
|
3
4
|
/** A run of 16 or more digits, which membrane's XML parser passes as text for
|
|
@@ -79,20 +80,20 @@ export function markedInputRefusal(call: { name: string; unparseableInput?: stri
|
|
|
79
80
|
// object. A block still open when a stream breaks the API's block order
|
|
80
81
|
// is marked the same way, and one that had stopped with its object's JSON,
|
|
81
82
|
// so the arguments may be another call's (ToolUseContent.unparseableInput).
|
|
82
|
-
if (/^[ \t\n\r]*$/.test(text)) return `Nothing was done: ${call.name} its arguments never arrived; call it again with them whole.`;
|
|
83
|
+
if (/^[ \t\n\r]*$/.test(text)) return `Nothing was done: ${nameToTypeBack(call.name)} its arguments never arrived; call it again with them whole.`;
|
|
83
84
|
let parsed: unknown;
|
|
84
85
|
try {
|
|
85
86
|
parsed = JSON.parse(text);
|
|
86
87
|
} catch {
|
|
87
|
-
return `Nothing was done: ${call.name} its arguments must be one JSON object, and this call gave text that isn't JSON, which may have been cut off: ${shown(text)}; call it again with one JSON object.`;
|
|
88
|
+
return `Nothing was done: ${nameToTypeBack(call.name)} its arguments must be one JSON object, and this call gave text that isn't JSON, which may have been cut off: ${shown(text)}; call it again with one JSON object.`;
|
|
88
89
|
}
|
|
89
90
|
if (parsed === null) return undefined;
|
|
90
|
-
if (typeof parsed !== 'object' || Array.isArray(parsed)) return `Nothing was done: ${call.name} ${notAnObject(parsed)}.`;
|
|
91
|
-
return `Nothing was done: ${call.name} its arguments came in a response that didn't arrive whole and in order, so they can't be taken as this call's; call it again.`;
|
|
91
|
+
if (typeof parsed !== 'object' || Array.isArray(parsed)) return `Nothing was done: ${nameToTypeBack(call.name)} ${notAnObject(parsed)}.`;
|
|
92
|
+
return `Nothing was done: ${nameToTypeBack(call.name)} its arguments came in a response that didn't arrive whole and in order, so they can't be taken as this call's; call it again.`;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
export function cutOffRefusal(call: { name: string }): string {
|
|
95
|
-
return `Nothing was done: ${call.name} didn't run, because the response it was in was cut off at the output limit. ` +
|
|
96
|
+
return `Nothing was done: ${nameToTypeBack(call.name)} didn't run, because the response it was in was cut off at the output limit. ` +
|
|
96
97
|
'To make the call, send less in one response: shorter text before it, shorter arguments, or fewer calls at once.';
|
|
97
98
|
}
|
|
98
99
|
|
|
@@ -216,7 +217,7 @@ export function toolArgsProblems(
|
|
|
216
217
|
const kinds = admittedKinds(declared(key));
|
|
217
218
|
if (value !== undefined && (value !== null || takesNull(declared(key)))) continue;
|
|
218
219
|
missing.add(key);
|
|
219
|
-
const wanted = kinds.length > 0 ?
|
|
220
|
+
const wanted = kinds.length > 0 ? `${quotedValue(key)} (${oneOf(kinds.map(withArticle))})` : quotedValue(key);
|
|
220
221
|
problems.push(`needs ${wanted}, and this call gave ${value === null ? 'null' : 'none'}`);
|
|
221
222
|
}
|
|
222
223
|
if (missing.size > 0 && declaresKeysPlainly(schema as Record<string, unknown>)) {
|
|
@@ -235,11 +236,11 @@ export function toolArgsProblems(
|
|
|
235
236
|
const kind = kindOf(value);
|
|
236
237
|
const largeInteger = kind === 'string' && kinds.includes('number') && LARGE_INTEGER_TEXT.test(value as string);
|
|
237
238
|
if (kinds.length > 0 && !kinds.includes(kind) && !largeInteger) {
|
|
238
|
-
problems.push(
|
|
239
|
+
problems.push(`${quotedValue(key)} must be ${oneOf(kinds.map(withArticle))}, and this call gave ${withArticle(kind)}`);
|
|
239
240
|
continue;
|
|
240
241
|
}
|
|
241
242
|
if (Array.isArray(param.enum) && !param.enum.includes(value)) {
|
|
242
|
-
problems.push(
|
|
243
|
+
problems.push(`${quotedValue(key)} must be one of ${oneOf(param.enum.map((e) => quotedValue(e)))}, and this call gave ${shown(value)}`);
|
|
243
244
|
}
|
|
244
245
|
}
|
|
245
246
|
return problems;
|
package/src/tool-image-ledger.ts
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* of bytes from another surface is the failure this exists to end.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
+
import { NOTICE_TAG } from './notice-tags.js';
|
|
29
30
|
import { createHash, randomBytes } from 'node:crypto';
|
|
30
31
|
import { REFERENCE_STUB_MAX_CHARS, referenceTypeLabel } from './mcpl/references.js';
|
|
31
32
|
import { safeSlice } from './safe-slice.js';
|
|
@@ -256,7 +257,7 @@ export type ParsedImagePlaceholder =
|
|
|
256
257
|
const PLACEHOLDER_RE = /\[image: ([a-z0-9!#$&^_.+-]{1,127}\/[a-z0-9!#$&^_.+-]{1,127}), (~?[\d.]{1,16}(?:B|KB|MB))(?:, ref (img_[0-9a-z]{1,16}_\d{1,16}))?\]/g;
|
|
257
258
|
/** `[ref_1_ab3d] cam.png — image/png, 1.2MB claimed — from tool result — …`
|
|
258
259
|
* (one line, at most REFERENCE_STUB_MAX_CHARS long: see parseImagePlaceholders). */
|
|
259
|
-
const REFERENCE_STUB_RE =
|
|
260
|
+
const REFERENCE_STUB_RE = new RegExp(`^\\[(${NOTICE_TAG.referenceId}_[0-9a-z]+_[0-9a-z]+)\\] ([^\\n]*)$`, 'gm');
|
|
260
261
|
const IMAGE_MIME_RE = /\bimage\/[a-z0-9!#$&^_.+-]+/i;
|
|
261
262
|
|
|
262
263
|
/**
|
package/src/tool-presentation.ts
CHANGED
|
@@ -32,7 +32,8 @@ const MAX_BYTES = 256 * 1024;
|
|
|
32
32
|
*/
|
|
33
33
|
const NONBLOCK = typeof constants.O_NONBLOCK === 'number' ? constants.O_NONBLOCK : 0;
|
|
34
34
|
const MAX_DESCRIPTION_CHARS = 32768;
|
|
35
|
-
|
|
35
|
+
// The toolsets meta-tools are how a hidden tool is reached again, so they stay.
|
|
36
|
+
const protectedNames = new Set(['set_tool_visibility', 'set_tool_description', 'workspace--read', 'toolsets--open', 'toolsets--act', 'toolsets--look', 'toolsets--close']);
|
|
36
37
|
export const isPresentationTool = (name: string): boolean => name === 'set_tool_visibility' || name === 'set_tool_description';
|
|
37
38
|
const hash = (value: string): string => createHash('sha256').update(value).digest('hex');
|
|
38
39
|
|
|
@@ -175,7 +176,7 @@ function lockLeftBehind(error: unknown): string {
|
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
export function presentationTools(path: string): ToolDefinition[] {
|
|
178
|
-
const signpost = ` Read the generated catalogue with workspace--read {"path":${JSON.stringify(path)},"limit":140}.
|
|
179
|
+
const signpost = ` Read the generated catalogue with workspace--read {"path":${JSON.stringify(path)},"limit":140}. A hidden tool moves out of your tool list into the host's "settings" toolset (an MCPL server's tool, into that server's tab): toolsets--look lists it there, and toolsets--act calls it once that toolset is open. Changes affect the next newly compiled request, not an in-flight stream.`;
|
|
179
180
|
return [
|
|
180
181
|
{name:'set_tool_visibility', description:'Show or hide a tool definition. Hiding does not revoke permission.' + signpost,
|
|
181
182
|
inputSchema:{type:'object',properties:{name:{type:'string'},visible:{type:'boolean'}},required:['name','visible'],additionalProperties:false}},
|
|
@@ -341,7 +342,7 @@ export class ToolPresentation {
|
|
|
341
342
|
if (field === 'description' && typeof args.description === 'string' && characters(args.description) > MAX_DESCRIPTION_CHARS)
|
|
342
343
|
throw new PresentationRefusal(`Nothing was changed: the description is ${count(characters(args.description))} characters, over the ${count(MAX_DESCRIPTION_CHARS)} allowed. Shorten it and call set_tool_description again.`);
|
|
343
344
|
if (field === 'visible' && args.visible === false && protectedNames.has(name))
|
|
344
|
-
throw new PresentationRefusal(`Nothing was changed: ${name} can't be hidden, since workspace--read, set_tool_visibility and
|
|
345
|
+
throw new PresentationRefusal(`Nothing was changed: ${name} can't be hidden, since workspace--read, set_tool_visibility, set_tool_description and the toolsets-- tools are how you read the catalogue, reach hidden tools and undo these changes. Calling this again won't change that.`);
|
|
345
346
|
return name;
|
|
346
347
|
}
|
|
347
348
|
}
|
|
@@ -356,7 +357,7 @@ export function renderCatalogue(snapshot: PresentationSnapshot): string {
|
|
|
356
357
|
'# Tool catalogue — generated from currently available tools', '',
|
|
357
358
|
`Revision: ${snapshot.revision}`, '',
|
|
358
359
|
`${snapshot.entries.length} tools: ${visible} visible, ${snapshot.entries.length-visible} hidden.`,
|
|
359
|
-
'Visible =
|
|
360
|
+
'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.',
|
|
360
361
|
'These are your visibility choices, not fixed primary/secondary rankings.',
|
|
361
362
|
'Copy exact names from the index; similar-sounding names may not exist.',
|
|
362
363
|
'Show a hidden tool: set_tool_visibility {"name":"EXACT_NAME","visible":true}.',
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* lifecycle facts (the [Gate: …] precedent).
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { NOTICE_TAG } from '../notice-tags.js';
|
|
19
|
+
import { cutToFit, suppliedValue } from '../supplied-text.js';
|
|
18
20
|
import { randomUUID } from 'node:crypto';
|
|
19
21
|
import type { ContentBlock } from '@animalabs/membrane';
|
|
20
22
|
import type { StoredMessage } from '@animalabs/context-manager';
|
|
@@ -38,14 +40,14 @@ const WAKE_COALESCE_MS = 2_000;
|
|
|
38
40
|
const EXPIRY_RETRY_MS = 60_000;
|
|
39
41
|
|
|
40
42
|
/** Ends a backlog line cut to fit in one of the residents' messages. */
|
|
41
|
-
const CUT_MARK =
|
|
43
|
+
const CUT_MARK = ` [${NOTICE_TAG.cutToFit}; the whole message is in the channel's history]`;
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
46
|
* tune_out's refusal of a channel already tuned out: its settings, then what
|
|
45
47
|
* its transport is now where the repeat tried to open it again (`transport`,
|
|
46
48
|
* from the framework's dispatch), then how to change the settings.
|
|
47
49
|
*/
|
|
48
|
-
export function alreadyTunedOutText(
|
|
50
|
+
export function alreadyTunedOutText(quoted: string, p: TuneOutParams, transport?: string): string {
|
|
49
51
|
// A duration so long its end is past what a Date can hold has no time to show.
|
|
50
52
|
const expires = p.expiresAtMs !== undefined ? new Date(p.expiresAtMs) : undefined;
|
|
51
53
|
const until = expires && Number.isFinite(expires.getTime())
|
|
@@ -53,21 +55,13 @@ export function alreadyTunedOutText(shown: string, p: TuneOutParams, transport?:
|
|
|
53
55
|
: '';
|
|
54
56
|
// A thrown error's words may end without a stop.
|
|
55
57
|
const now = transport === undefined ? '' : /[.!?]$/.test(transport) ? `${transport} ` : `${transport}. `;
|
|
56
|
-
return `Nothing changed${transport !== undefined ? ' in the tune-out' : ''}: ${
|
|
58
|
+
return `Nothing changed${transport !== undefined ? ' in the tune-out' : ''}: ${quoted} is already `
|
|
57
59
|
+ `tuned out, with cadenceSeconds ${p.cadenceSeconds}, backlogCap ${p.backlogCap} and maxWakes ${p.maxWakes}${until}. `
|
|
58
60
|
+ now
|
|
59
61
|
+ 'To change those, cancel it first with tune_out mode "cancel", which delivers its backlog to you now, then call '
|
|
60
62
|
+ 'tune_out again with the new settings.';
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
/** The first `length` code units of `text`, one fewer where the last would be
|
|
64
|
-
* the first half of a surrogate pair. */
|
|
65
|
-
function prefixOf(text: string, length: number): string {
|
|
66
|
-
const cut = text.slice(0, Math.max(0, length));
|
|
67
|
-
const last = cut.charCodeAt(cut.length - 1);
|
|
68
|
-
return last >= 0xd800 && last <= 0xdbff ? cut.slice(0, -1) : cut;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
65
|
/** What a thrown value says, without throwing: an Error's message, or the
|
|
72
66
|
* value as text, which String() can't give for an object with no prototype. */
|
|
73
67
|
function thrownReason(error: unknown): string {
|
|
@@ -153,6 +147,10 @@ export class TuneOutCoordinator {
|
|
|
153
147
|
return this.channelRegistry.nameShown({ serverId, channelId });
|
|
154
148
|
}
|
|
155
149
|
|
|
150
|
+
private quotedShown(serverId: string, channelId: string): string {
|
|
151
|
+
return this.channelRegistry.quotedShown({ serverId, channelId });
|
|
152
|
+
}
|
|
153
|
+
|
|
156
154
|
/** Restart cadence + expiry timers and re-anchor after a process restart. */
|
|
157
155
|
resumeActiveEpochs(): void {
|
|
158
156
|
let oldestAnchor: number | null = null;
|
|
@@ -244,14 +242,14 @@ export class TuneOutCoordinator {
|
|
|
244
242
|
if (this.hooks.isForkBound({ serverId, channelId })) {
|
|
245
243
|
return {
|
|
246
244
|
ok: false,
|
|
247
|
-
error: `Nothing changed: a conversation is bound to ${
|
|
245
|
+
error: `Nothing changed: a conversation is bound to ${this.quotedShown(serverId, channelId)}, so its messages go to that `
|
|
248
246
|
+ "conversation, and tune_out won't divert a channel while one is. Calling tune_out on it again won't change "
|
|
249
247
|
+ 'that until the conversation ends, which happens only once the channel has been quiet for a while. Tune '
|
|
250
248
|
+ 'out a different channel, or ask whoever runs this host if this one should be diverted.',
|
|
251
249
|
};
|
|
252
250
|
}
|
|
253
251
|
const current = this.channelRegistry.getTuneOutState(serverId, channelId);
|
|
254
|
-
if (current) return { ok: false, error: alreadyTunedOutText(this.
|
|
252
|
+
if (current) return { ok: false, error: alreadyTunedOutText(this.quotedShown(serverId, channelId), current.params), alreadyTunedOut: true };
|
|
255
253
|
// A tool's input reaches here as the model wrote it, and the store can't
|
|
256
254
|
// keep a NaN or an Infinity: its append would throw after the registry
|
|
257
255
|
// had marked the channel tuned out. So each option is read as a number
|
|
@@ -337,7 +335,7 @@ export class TuneOutCoordinator {
|
|
|
337
335
|
const ended = this.channelRegistry.cancelTuneOut(serverId, channelId, 'open', source);
|
|
338
336
|
if (!ended) {
|
|
339
337
|
// Its reader is the resident's tune_out: cancel_tuneout finds the channel first.
|
|
340
|
-
return { ok: false, error: this.notTunedOut(this.
|
|
338
|
+
return { ok: false, error: this.notTunedOut(this.quotedShown(serverId, channelId), 'you') };
|
|
341
339
|
}
|
|
342
340
|
this.clearTimers(serverId, channelId);
|
|
343
341
|
this.divertedSinceInvocation.delete(this.key(serverId, channelId));
|
|
@@ -360,7 +358,7 @@ export class TuneOutCoordinator {
|
|
|
360
358
|
// start at 0 and take them all.
|
|
361
359
|
const cap = Math.floor(ended.params.backlogCap);
|
|
362
360
|
const authorOf = (m: Pick<StoredMessage, 'participant' | 'metadata'>): string =>
|
|
363
|
-
(m.metadata as { author?: { name?: string } } | undefined)?.author?.name ?? m.participant;
|
|
361
|
+
suppliedValue((m.metadata as { author?: { name?: string } } | undefined)?.author?.name ?? m.participant);
|
|
364
362
|
const lineOf = (m: Pick<StoredMessage, 'participant' | 'content' | 'metadata'>): string => {
|
|
365
363
|
const text = m.content
|
|
366
364
|
.filter((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text')
|
|
@@ -370,10 +368,10 @@ export class TuneOutCoordinator {
|
|
|
370
368
|
};
|
|
371
369
|
// The channel as the resident and the subconscious are shown it.
|
|
372
370
|
const name = this.shown(serverId, channelId);
|
|
373
|
-
const lifecycle = `[
|
|
371
|
+
const lifecycle = `[${NOTICE_TAG.tuneOut}: ${name} cancelled — ${reason}, ${ended.wakeCount} wake${ended.wakeCount === 1 ? '' : 's'}]`;
|
|
374
372
|
const render = (lines: string[], truncated: number): string => {
|
|
375
373
|
const attrs = [
|
|
376
|
-
`channel
|
|
374
|
+
`channel=${this.quotedShown(serverId, channelId)}`,
|
|
377
375
|
`messages=${backlog.length}`,
|
|
378
376
|
...(truncated > 0 ? [`truncated=${truncated} (oldest; fetch_history covers the rest)`] : []),
|
|
379
377
|
];
|
|
@@ -417,7 +415,7 @@ export class TuneOutCoordinator {
|
|
|
417
415
|
// What of the newest line's text fits beside the frame and the mark. A
|
|
418
416
|
// cut shows some of it or none: a room under one character, or one
|
|
419
417
|
// that would split a surrogate pair, leaves the head empty.
|
|
420
|
-
const head =
|
|
418
|
+
const head = cutToFit(lines.at(-1) ?? '', room - frameShowing(1) - CUT_MARK.length);
|
|
421
419
|
if (kept === 0 && head !== '') {
|
|
422
420
|
lines = [head + CUT_MARK];
|
|
423
421
|
shown = shown.slice(-1);
|
|
@@ -480,7 +478,7 @@ export class TuneOutCoordinator {
|
|
|
480
478
|
: '';
|
|
481
479
|
this.hooks.addMessage(
|
|
482
480
|
'user',
|
|
483
|
-
[{ type: 'text', text: `[
|
|
481
|
+
[{ type: 'text', text: `[${NOTICE_TAG.tuneOutCancelled}: ${this.shown(serverId, channelId)} — ${reason}.${truncNote}${heldNote}]` }],
|
|
484
482
|
{ system: true, kind: 'tune-out-cancel-report', channelId },
|
|
485
483
|
subName,
|
|
486
484
|
);
|
|
@@ -664,8 +662,8 @@ export class TuneOutCoordinator {
|
|
|
664
662
|
|
|
665
663
|
const shown = this.shown(serverId, channelId);
|
|
666
664
|
const notice = trigger === 'wake'
|
|
667
|
-
? `[
|
|
668
|
-
: `[
|
|
665
|
+
? `[${NOTICE_TAG.tuneOutWake}: ${shown} — ${count} addressed message${count === 1 ? '' : 's'}]`
|
|
666
|
+
: `[${NOTICE_TAG.tuneOutCadence}: ${shown} — ${count} diverted message${count === 1 ? '' : 's'} since your last look]`;
|
|
669
667
|
this.hooks.addMessage(
|
|
670
668
|
'user',
|
|
671
669
|
[{ type: 'text', text: notice }],
|
|
@@ -709,7 +707,7 @@ export class TuneOutCoordinator {
|
|
|
709
707
|
position: 'system',
|
|
710
708
|
content: [{
|
|
711
709
|
type: 'text',
|
|
712
|
-
text: `[
|
|
710
|
+
text: `[${NOTICE_TAG.standingDispositions}]\n${entries.map(([k, v]) => `- ${k}: ${v}`).join('\n')}`,
|
|
713
711
|
}],
|
|
714
712
|
};
|
|
715
713
|
}
|
|
@@ -809,7 +807,7 @@ export class TuneOutCoordinator {
|
|
|
809
807
|
if (shared) return { success: false, error: shared, isError: true };
|
|
810
808
|
const channel = named[0];
|
|
811
809
|
if (!channel || !this.channelRegistry.getTuneOutState(channel.serverId, channel.channelId)) {
|
|
812
|
-
return { success: false, error: this.notTunedOut(name, 'the resident'), isError: true };
|
|
810
|
+
return { success: false, error: this.notTunedOut(JSON.stringify(name), 'the resident'), isError: true };
|
|
813
811
|
}
|
|
814
812
|
const note = typeof input.text === 'string' && input.text.trim() ? input.text.trim() : undefined;
|
|
815
813
|
const result = this.cancel(channel.serverId, channel.channelId, 'subconscious', 'subconscious judgment', note);
|
|
@@ -874,7 +872,7 @@ export class TuneOutCoordinator {
|
|
|
874
872
|
}
|
|
875
873
|
|
|
876
874
|
/**
|
|
877
|
-
* A cancel of a channel that isn't tuned out, by the name the caller gave
|
|
875
|
+
* A cancel of a channel that isn't tuned out, by the name, quoted, the caller gave
|
|
878
876
|
* or knows it by: which channels are, so the next call can name one, and
|
|
879
877
|
* how a tune-out ends without one. `backlogTo`
|
|
880
878
|
* names the resident as the message's reader knows it: "you" to the
|
|
@@ -884,11 +882,11 @@ export class TuneOutCoordinator {
|
|
|
884
882
|
* neither this list nor the name's lookup (channelsNamed, which reads the
|
|
885
883
|
* registered channels too), and the text claims nothing about it.
|
|
886
884
|
*/
|
|
887
|
-
private notTunedOut(
|
|
888
|
-
const active = this.listActive().map((e) =>
|
|
885
|
+
private notTunedOut(quoted: string, backlogTo: 'you' | 'the resident'): string {
|
|
886
|
+
const active = this.listActive().map((e) => this.quotedShown(e.serverId, e.channelId));
|
|
889
887
|
const opening = active.length === 0
|
|
890
888
|
? 'no connected channel is tuned out now.'
|
|
891
|
-
: `no connected channel ${
|
|
889
|
+
: `no connected channel ${quoted} is tuned out. Connected channels tuned out now: `
|
|
892
890
|
+ `${active.join(', ')}.`;
|
|
893
891
|
return `Nothing was cancelled: ${opening} A tune-out also ends by itself when its duration runs out, or at the `
|
|
894
892
|
+ `first wake past its wake budget, and its backlog goes to ${backlogTo} then.`;
|
package/src/types/agent.ts
CHANGED
|
@@ -55,13 +55,15 @@ export interface AgentConfig {
|
|
|
55
55
|
* Enforced at dispatch for registered agent callers; omitted means 'all'.
|
|
56
56
|
* - 'all': all available tools
|
|
57
57
|
* - string[]: exact tool names, including module--utility names for utilities.
|
|
58
|
-
* Utilities
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* '
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
58
|
+
* Utilities and the host's other rarely-used tools are in the host's
|
|
59
|
+
* built-in toolset ("settings"), out of the tool list: the model reaches
|
|
60
|
+
* them with toolsets--open {toolset:"settings"} and toolsets--act, each
|
|
61
|
+
* checked under the tool's own name. An agent that should use a utility
|
|
62
|
+
* from its model lists 'toolsets--open', 'toolsets--act' and
|
|
63
|
+
* 'module--utility' (an agent without the two meta-tools keeps every other
|
|
64
|
+
* tool in its list, but no utility). The name alone passes the dispatch
|
|
65
|
+
* check for any call by that name, whether the host makes it with
|
|
66
|
+
* executeToolCall on the agent's behalf or the model or a script makes it.
|
|
65
67
|
* Trusted module calls are exempt. Genuinely unidentified public callers,
|
|
66
68
|
* including unknown legacy names, remain unrestricted. Framework-created
|
|
67
69
|
* identities have tool authority only while registered.
|
|
@@ -580,8 +582,9 @@ export interface InferenceRequest {
|
|
|
580
582
|
* the turn's auto-published plain-text speech here so a single TRUNK agent
|
|
581
583
|
* replies in the channel it is answering — not the process-global most-recent-
|
|
582
584
|
* inbound locus, which a concurrent message elsewhere can hijack (item-3
|
|
583
|
-
* redux). Undefined for non-channel wakes (heartbeat, timers, module events)
|
|
584
|
-
*
|
|
585
|
+
* redux). Undefined for non-channel wakes (heartbeat, timers, module events).
|
|
586
|
+
* A heartbeat's turn takes no channel at all, and holds its plain speech as
|
|
587
|
+
* drafts; any other falls back to the global default channel, except an
|
|
585
588
|
* ephemeral run's (a subagent's), which takes no global fallback.
|
|
586
589
|
*/
|
|
587
590
|
channelId?: string;
|
|
@@ -604,6 +607,17 @@ export interface InferenceRequest {
|
|
|
604
607
|
* it starts. Hosts don't set it.
|
|
605
608
|
*/
|
|
606
609
|
autoContinueRun?: AutoContinueRun;
|
|
610
|
+
/**
|
|
611
|
+
* Framework-set: whether this wake's turn is a heartbeat's, which has no
|
|
612
|
+
* channel and holds its plain speech as drafts. True on a wake that
|
|
613
|
+
* continues a heartbeat's turn with no channel of its own (an autoContinue
|
|
614
|
+
* continuation, a bounce's resend wake, a ` !` continuation, and a re-arm,
|
|
615
|
+
* which copies its trigger), and on a merged batch whose causes include a
|
|
616
|
+
* heartbeat's wake and name no channel; false on a merged batch with none,
|
|
617
|
+
* so the batch's first request doesn't decide it. Unset, a wake is a
|
|
618
|
+
* heartbeat's when its `source` is `heartbeat`. Hosts don't set it.
|
|
619
|
+
*/
|
|
620
|
+
heartbeatTurn?: boolean;
|
|
607
621
|
/**
|
|
608
622
|
* True when the triggering message explicitly addressed the agent (mention,
|
|
609
623
|
* reply-to-bot, DM — `chat:addressed` in MCPL RFC-001 terms). When a wake
|
package/src/types/framework.ts
CHANGED
|
@@ -218,6 +218,50 @@ export interface FrameworkConfig {
|
|
|
218
218
|
*/
|
|
219
219
|
providerHold?: ProviderHoldHook;
|
|
220
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Delays before a turn whose retries ran out is sent again, in ms (default
|
|
223
|
+
* `[120000, 480000, 1920000]`: 2, 8 and 32 minutes), or `false` never to.
|
|
224
|
+
* When a persistent agent's turn ends exhausted on a transient failure (a
|
|
225
|
+
* server error, an overload, a rate limit, a network failure, or a timeout
|
|
226
|
+
* membrane marks retryable and not as a deadline it reports as run out, such
|
|
227
|
+
* as a 408 or a socket's ETIMEDOUT), its trigger is queued again after the
|
|
228
|
+
* first delay, as a wake of its own. It opens where the exhausted turn's
|
|
229
|
+
* plain speech was going: its locus, where a message mid-turn may have moved
|
|
230
|
+
* it, else its trigger channel. Carrying that channel, it ranks as addressed
|
|
231
|
+
* in a merged wake's choice of channel, so a newer ambient wake from
|
|
232
|
+
* elsewhere doesn't take it; an autoContinue continuation's still yields its
|
|
233
|
+
* channel to any wake's, as it does on main; a continuation's, such as a
|
|
234
|
+
* budget restart's, names no author. A re-arm that would have to guess a
|
|
235
|
+
* channel isn't sent again: one whose turn was woken for a channel id that
|
|
236
|
+
* names no one registered channel and never found one, and whose request
|
|
237
|
+
* names none of its own. A failure that states a wait is held for that wait
|
|
238
|
+
* first, as any turn's is, and a deadline ends the turn as before, whether
|
|
239
|
+
* the host set it or membrane marks one as run out: the runtime's or
|
|
240
|
+
* undici's on a sent request, the Anthropic adapter's watchdogs, or the
|
|
241
|
+
* Anthropic SDK's timeout, which covers its connect timeout too. Until one
|
|
242
|
+
* of the re-armed turn's rounds has run or had its tool calls cut off, its
|
|
243
|
+
* failure gets no retries from the error policy: on another transient
|
|
244
|
+
* failure the next delay follows, until the list runs out, and a failure of
|
|
245
|
+
* any other kind ends the re-arming. A wake for the agent while it waits
|
|
246
|
+
* starts its turn at once, with the re-armed trigger merged in. That turn is
|
|
247
|
+
* the wake's: the error policy retries its failures. A completed turn starts
|
|
248
|
+
* the count again, and so does a message said to the agent: not a system
|
|
249
|
+
* message or notice; not an edit, reaction or deletion, unless an edit is
|
|
250
|
+
* the first version of a message the agent sees; not a message its own view
|
|
251
|
+
* leaves out, such as one a tune-out holds from it; and not the agent's own
|
|
252
|
+
* schedule, the heartbeat's tick or reminder, a self-wake timer it set, or a
|
|
253
|
+
* script's wake. A notice a module or an MCPL server stores as a message
|
|
254
|
+
* counts. A message in the residents' shared slot counts for each resident
|
|
255
|
+
* that reads it. Any other wake leaves the count going on. A re-armed turn's
|
|
256
|
+
* transient failure writes no `[inference-failed]` marker unless a round of
|
|
257
|
+
* it ran or had its tool calls cut off; any other failure of it writes one.
|
|
258
|
+
* Each counts toward the hard-down streak. Ephemeral runs and conversation
|
|
259
|
+
* forks are never re-armed. The wait and the count are kept in memory:
|
|
260
|
+
* `stop()` drops them, and so does a restart. The wait isn't a turn, so
|
|
261
|
+
* `abortInference()` doesn't end it.
|
|
262
|
+
*/
|
|
263
|
+
exhaustedTurnRearmMs?: number[] | false;
|
|
264
|
+
|
|
221
265
|
/** Interval for periodic store sync in milliseconds (default: 1000ms, 0 to disable) */
|
|
222
266
|
syncIntervalMs?: number;
|
|
223
267
|
|
package/src/types/module.ts
CHANGED
|
@@ -72,10 +72,11 @@ export type ModuleMessage = StoredMessage & {
|
|
|
72
72
|
* True on a message a tune-out holds from the residents: one that arrived
|
|
73
73
|
* in a tuned-out channel, stamped `metadata.tuneOut` as it came in. The
|
|
74
74
|
* store keeps it, but no resident is shown the row itself: no resident's
|
|
75
|
-
* compiled view includes it, during the tune-out or after it, and a
|
|
76
|
-
* boundary of a turn alive when it arrived stores it
|
|
77
|
-
* into the live turn. The subconscious reads it. Its
|
|
78
|
-
* a resident in rows of their own, which aren't
|
|
75
|
+
* compiled view includes it, during the tune-out or after it, and a
|
|
76
|
+
* resident's tool boundary of a turn alive when it arrived stores it
|
|
77
|
+
* without injecting it into the live turn. The subconscious reads it. Its
|
|
78
|
+
* text can still reach a resident in rows of their own, which aren't
|
|
79
|
+
* marked, such as the
|
|
79
80
|
* cancel's `<tuned-out-backlog>` row, with the text of the tune-out's newest
|
|
80
81
|
* messages, up to `backlogCap` of them and as many as fit in one of the
|
|
81
82
|
* residents' messages, the stored ones first and then those still held;
|
|
@@ -142,16 +143,27 @@ export interface Module {
|
|
|
142
143
|
|
|
143
144
|
/**
|
|
144
145
|
* Utilities: agent-invocable operations that deliberately do NOT get a
|
|
145
|
-
*
|
|
146
|
-
* capability used twice a month shouldn't. Utilities
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
146
|
+
* slot in the tool list. Every tool schema taxes every inference; a
|
|
147
|
+
* capability used twice a month shouldn't. Utilities belong to the host's
|
|
148
|
+
* built-in toolset ("settings"): an agent sees them with
|
|
149
|
+
* `toolsets--look {toolset:"settings"}` and calls them with
|
|
150
|
+
* `toolsets--act` once it has opened it. Same ToolDefinition shape, same
|
|
151
|
+
* un-prefixed names, and dispatch arrives at the SAME handleToolCall as
|
|
152
|
+
* getTools() output. Optional; omitting it means the module contributes no
|
|
153
|
+
* utilities.
|
|
152
154
|
*/
|
|
153
155
|
getUtilities?(): ToolDefinition[];
|
|
154
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Where one of this module's tools (a getTools() name, unprefixed) is
|
|
159
|
+
* presented: in the agent's tool list (`'header'`, the default), or in the
|
|
160
|
+
* host's built-in toolset (`'builtin'`), out of the tool list, opened with
|
|
161
|
+
* `toolsets--open {toolset:"settings"}` and called with `toolsets--act`.
|
|
162
|
+
* For tools an agent uses rarely: settings, administration, status reads.
|
|
163
|
+
* Optional; omitting it keeps every tool in the tool list.
|
|
164
|
+
*/
|
|
165
|
+
toolPlacement?(toolName: string): 'header' | 'builtin' | undefined;
|
|
166
|
+
|
|
155
167
|
/**
|
|
156
168
|
* Handle a tool call.
|
|
157
169
|
* Tool name is without module prefix.
|