@animalabs/agent-framework 0.29.0 → 0.30.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/admission/admit.d.ts +90 -0
- package/dist/src/admission/admit.d.ts.map +1 -0
- package/dist/src/admission/admit.js +43 -0
- package/dist/src/admission/admit.js.map +1 -0
- package/dist/src/agent.d.ts +21 -0
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +43 -6
- package/dist/src/agent.js.map +1 -1
- package/dist/src/author-presentation.d.ts +50 -0
- package/dist/src/author-presentation.d.ts.map +1 -0
- package/dist/src/author-presentation.js +58 -0
- package/dist/src/author-presentation.js.map +1 -0
- package/dist/src/dendrite/index.d.ts +6 -0
- package/dist/src/dendrite/index.d.ts.map +1 -0
- package/dist/src/dendrite/index.js +3 -0
- package/dist/src/dendrite/index.js.map +1 -0
- package/dist/src/dendrite/presets.d.ts +60 -0
- package/dist/src/dendrite/presets.d.ts.map +1 -0
- package/dist/src/dendrite/presets.js +117 -0
- package/dist/src/dendrite/presets.js.map +1 -0
- package/dist/src/dendrite/registry.d.ts +260 -0
- package/dist/src/dendrite/registry.d.ts.map +1 -0
- package/dist/src/dendrite/registry.js +742 -0
- package/dist/src/dendrite/registry.js.map +1 -0
- package/dist/src/dendrite/types.d.ts +392 -0
- package/dist/src/dendrite/types.d.ts.map +1 -0
- package/dist/src/dendrite/types.js +24 -0
- package/dist/src/dendrite/types.js.map +1 -0
- package/dist/src/framework.d.ts +684 -33
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +2998 -350
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +12 -4
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +25 -5
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/host-presentation.d.ts +4 -0
- package/dist/src/host-presentation.d.ts.map +1 -1
- package/dist/src/host-presentation.js +7 -3
- package/dist/src/host-presentation.js.map +1 -1
- package/dist/src/index.d.ts +5 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +84 -9
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +136 -58
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +9 -0
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +21 -0
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +7 -0
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/notice-tags.d.ts +2 -0
- package/dist/src/notice-tags.d.ts.map +1 -1
- package/dist/src/notice-tags.js +2 -0
- package/dist/src/notice-tags.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +4 -2
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +7 -4
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/stored-injections.d.ts +4 -0
- package/dist/src/stored-injections.d.ts.map +1 -1
- package/dist/src/stored-injections.js +6 -0
- package/dist/src/stored-injections.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +379 -9
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +1080 -97
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +101 -5
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js +55 -18
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/types/agent.d.ts +25 -0
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +99 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +136 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/admission/admit.ts +113 -0
- package/src/agent.ts +48 -6
- package/src/author-presentation.ts +81 -0
- package/src/dendrite/index.ts +32 -0
- package/src/dendrite/presets.ts +149 -0
- package/src/dendrite/registry.ts +808 -0
- package/src/dendrite/types.ts +397 -0
- package/src/framework.ts +3239 -369
- package/src/gate/event-gate.ts +31 -5
- package/src/host-presentation.ts +10 -3
- package/src/index.ts +15 -2
- package/src/mcpl/channel-registry.ts +170 -60
- package/src/mcpl/push-coalescer.ts +19 -0
- package/src/mcpl/toolsets-module.ts +7 -0
- package/src/notice-tags.ts +2 -0
- package/src/silent-heartbeat.ts +7 -4
- package/src/stored-injections.ts +7 -0
- package/src/tune-out/coordinator.ts +1229 -98
- package/src/tune-out/tools.ts +152 -11
- package/src/types/agent.ts +28 -0
- package/src/types/framework.ts +102 -0
- package/src/types/trace.ts +150 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The author of a channel message, presented to the model.
|
|
3
|
+
*
|
|
4
|
+
* MCPL `channels/incoming` carries the author as a field, `author: {id,
|
|
5
|
+
* name}`, and the body alone as content (SPEC §14); the framework stores
|
|
6
|
+
* the message under the anonymous participant `user` with the author in
|
|
7
|
+
* `metadata.author`. The wire formatter writes the participant, so without
|
|
8
|
+
* this step the model reads `user: <text>` and who spoke is lost. Servers
|
|
9
|
+
* that pre-render `Name: body` into the text themselves (portal-mcpl) are
|
|
10
|
+
* left as they are: the rule is idempotent, so the name appears once
|
|
11
|
+
* whichever shape a server sends.
|
|
12
|
+
*
|
|
13
|
+
* Applied where the framework stores a `channels/incoming` message — the
|
|
14
|
+
* compiled view carries no metadata, so the name has to be in the stored
|
|
15
|
+
* text to reach the wire — and to a tune-out backlog line, which reads the
|
|
16
|
+
* metadata and must not name an author twice. The author stays in
|
|
17
|
+
* `metadata.author` as the structured record.
|
|
18
|
+
*/
|
|
19
|
+
import type { ContentBlock, MessageMetadata } from '@animalabs/membrane';
|
|
20
|
+
import { suppliedValue } from './supplied-text.js';
|
|
21
|
+
|
|
22
|
+
/** The participant the framework stores channel traffic under: a role, not a name. */
|
|
23
|
+
export const ANONYMOUS_PARTICIPANT = 'user';
|
|
24
|
+
|
|
25
|
+
type TextBlock = Extract<ContentBlock, { type: 'text' }>;
|
|
26
|
+
|
|
27
|
+
/** The author a stored message names, when it names one. */
|
|
28
|
+
export function authorNamed(metadata: Record<string, unknown> | MessageMetadata | undefined): string | undefined {
|
|
29
|
+
const name = (metadata?.author as { name?: unknown } | undefined)?.name;
|
|
30
|
+
return typeof name === 'string' && name.length > 0 ? name : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Whether `text` already begins with the author's own line: `Name:` then a space or the end. */
|
|
34
|
+
export function beginsWithAuthor(author: string, text: string): boolean {
|
|
35
|
+
if (!text.startsWith(author) || text.charAt(author.length) !== ':') return false;
|
|
36
|
+
const after = text.charAt(author.length + 1);
|
|
37
|
+
return after === '' || after === ' ' || after === '\n';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The author's name as a line of the framework's writes it: a value someone
|
|
42
|
+
* supplied, kept to one line with its notice openings marked (main's #852,
|
|
43
|
+
* suppliedValue), so a name can't open a line that reads as the framework's
|
|
44
|
+
* own notice.
|
|
45
|
+
*/
|
|
46
|
+
export function authorShown(author: string): string {
|
|
47
|
+
return suppliedValue(author);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Whether `text` begins with the author's line, as the author wrote their
|
|
51
|
+
* name or as the framework shows it (authorShown). */
|
|
52
|
+
export function beginsWithAuthorLine(author: string, text: string): boolean {
|
|
53
|
+
return beginsWithAuthor(author, text) || beginsWithAuthor(authorShown(author), text);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** `author: text`, unless `text` begins that way already. */
|
|
57
|
+
export function authorLine(author: string, text: string): string {
|
|
58
|
+
return beginsWithAuthorLine(author, text) ? text : `${authorShown(author)}: ${text}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A message stored under the anonymous participant that names an author is
|
|
63
|
+
* presented with the author before its first text block — or, with no text
|
|
64
|
+
* block, as a leading `Name:` line before its media. Any other message, and
|
|
65
|
+
* one whose text already begins with the author's line, is returned as is.
|
|
66
|
+
* Never mutates its input.
|
|
67
|
+
*/
|
|
68
|
+
export function presentAuthor<M extends { participant: string; content: ContentBlock[]; metadata?: MessageMetadata | Record<string, unknown> }>(message: M): M {
|
|
69
|
+
if (message.participant !== ANONYMOUS_PARTICIPANT) return message;
|
|
70
|
+
const author = authorNamed(message.metadata);
|
|
71
|
+
if (author === undefined) return message;
|
|
72
|
+
const index = message.content.findIndex((b) => b.type === 'text');
|
|
73
|
+
if (index === -1) {
|
|
74
|
+
return { ...message, content: [{ type: 'text', text: `${authorShown(author)}:` } as TextBlock, ...message.content] };
|
|
75
|
+
}
|
|
76
|
+
const block = message.content[index] as TextBlock;
|
|
77
|
+
if (typeof block.text !== 'string' || beginsWithAuthorLine(author, block.text)) return message;
|
|
78
|
+
const content = [...message.content];
|
|
79
|
+
content[index] = { ...block, text: `${authorShown(author)}: ${block.text}` };
|
|
80
|
+
return { ...message, content };
|
|
81
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export { AgentRegistry, AgentSpecError } from './registry.js';
|
|
2
|
+
export type { AgentRegistryOptions, RestoreOutcome, ReconcileOutcome } from './registry.js';
|
|
3
|
+
export {
|
|
4
|
+
residentSpec,
|
|
5
|
+
subconsciousSpec,
|
|
6
|
+
conversationForkSpec,
|
|
7
|
+
workerSpec,
|
|
8
|
+
taskForkSpec,
|
|
9
|
+
DEFAULT_TASK_IDLE_TIMEOUT_MS,
|
|
10
|
+
} from './presets.js';
|
|
11
|
+
export type { BoundedJobOptions } from './presets.js';
|
|
12
|
+
export type {
|
|
13
|
+
AgentName,
|
|
14
|
+
AgentKind,
|
|
15
|
+
AgentRoles,
|
|
16
|
+
AgentLifetime,
|
|
17
|
+
ActivationBounds,
|
|
18
|
+
ParentEndPolicy,
|
|
19
|
+
ResultRoute,
|
|
20
|
+
ObserveEdge,
|
|
21
|
+
AgentRelationships,
|
|
22
|
+
ContextInheritance,
|
|
23
|
+
AgentSpec,
|
|
24
|
+
AgentEndReason,
|
|
25
|
+
AgentRef,
|
|
26
|
+
ActivationRecord,
|
|
27
|
+
AgentRecord,
|
|
28
|
+
HeldMail,
|
|
29
|
+
RegistrySnapshot,
|
|
30
|
+
EndOutcome,
|
|
31
|
+
PolicyEpoch,
|
|
32
|
+
} from './types.js';
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presets — the historical agent kinds expressed as {@link AgentSpec}
|
|
3
|
+
* values. They are configurations of the common machinery, not engines:
|
|
4
|
+
* everything a preset decides is visible in the spec it returns, and a host
|
|
5
|
+
* may register a spec that matches none of them.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentName, AgentSpec, ContextInheritance, ResultRoute } from './types.js';
|
|
9
|
+
|
|
10
|
+
/** Default idle bound for a bounded job with no explicit deadline (matches the run watchdog). */
|
|
11
|
+
export const DEFAULT_TASK_IDLE_TIMEOUT_MS = 15 * 60_000;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A resident: persistent, declared in configuration, reads the residents'
|
|
15
|
+
* shared message slot. The first one declared owns default delivery.
|
|
16
|
+
*/
|
|
17
|
+
export function residentSpec(name: AgentName, options: { primary: boolean }): AgentSpec {
|
|
18
|
+
return {
|
|
19
|
+
name,
|
|
20
|
+
kind: 'resident',
|
|
21
|
+
roles: {
|
|
22
|
+
defaultDelivery: options.primary,
|
|
23
|
+
receivesUntargeted: true,
|
|
24
|
+
receivesGateWakes: true,
|
|
25
|
+
ownsProviderScheduling: true,
|
|
26
|
+
},
|
|
27
|
+
lifetime: { kind: 'persistent' },
|
|
28
|
+
readsSharedSlot: true,
|
|
29
|
+
namespace: `agents/${name}`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The subconscious: a persistent attention tenant of the resident it
|
|
35
|
+
* serves. It persists across policy epochs (cancelling a tune-out ends a
|
|
36
|
+
* channel's cadence, not this agent), observes the residents' slot with
|
|
37
|
+
* held traffic included, is never a broadcast recipient, and ends with its
|
|
38
|
+
* resident.
|
|
39
|
+
*/
|
|
40
|
+
export function subconsciousSpec(name: AgentName, serves: AgentName): AgentSpec {
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
kind: 'subconscious',
|
|
44
|
+
roles: {
|
|
45
|
+
defaultDelivery: false,
|
|
46
|
+
receivesUntargeted: false,
|
|
47
|
+
receivesGateWakes: false,
|
|
48
|
+
ownsProviderScheduling: true,
|
|
49
|
+
},
|
|
50
|
+
lifetime: { kind: 'persistent' },
|
|
51
|
+
spawnedBy: serves,
|
|
52
|
+
onParentEnd: 'end',
|
|
53
|
+
observes: [{ includeHeld: true }],
|
|
54
|
+
resultTo: { to: serves, as: 'message' },
|
|
55
|
+
readsSharedSlot: true,
|
|
56
|
+
namespace: `subconscious/${serves}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A conversation fork: derived from a template agent's context at bind
|
|
62
|
+
* time, bound to one channel, independent continuation, ends on idle.
|
|
63
|
+
*/
|
|
64
|
+
export function conversationForkSpec(
|
|
65
|
+
name: AgentName,
|
|
66
|
+
options: {
|
|
67
|
+
template: AgentName;
|
|
68
|
+
/** The channel it's bound to, on its server. */
|
|
69
|
+
homeChannel: { serverId: string; channelId: string };
|
|
70
|
+
idleTtlMs: number;
|
|
71
|
+
inherit?: Partial<ContextInheritance>;
|
|
72
|
+
},
|
|
73
|
+
): AgentSpec {
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
kind: 'conversation-fork',
|
|
77
|
+
roles: {
|
|
78
|
+
defaultDelivery: false,
|
|
79
|
+
receivesUntargeted: false,
|
|
80
|
+
// Woken by its own channel's debounced traffic through the host-wide gate.
|
|
81
|
+
receivesGateWakes: true,
|
|
82
|
+
ownsProviderScheduling: false,
|
|
83
|
+
},
|
|
84
|
+
lifetime: { kind: 'idle', idleTtlMs: options.idleTtlMs },
|
|
85
|
+
spawnedBy: options.template,
|
|
86
|
+
onParentEnd: 'orphan',
|
|
87
|
+
inherit: { from: options.template, mode: 'copy', ...options.inherit },
|
|
88
|
+
// A copy renames the template's turns as it copies them. A shared
|
|
89
|
+
// inheritance has no copy step, so the mapping is declared instead.
|
|
90
|
+
...(options.inherit?.mode === 'shared' ? { selfParticipants: [options.template] } : {}),
|
|
91
|
+
homeChannel: { serverId: options.homeChannel.serverId, channelId: options.homeChannel.channelId },
|
|
92
|
+
namespace: `conversations/${name}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface BoundedJobOptions {
|
|
97
|
+
/** Who created it. Absent for an independent job driven by the host. */
|
|
98
|
+
spawnedBy?: AgentName;
|
|
99
|
+
/** Where its completion returns. */
|
|
100
|
+
resultTo?: ResultRoute;
|
|
101
|
+
deadlineMs?: number;
|
|
102
|
+
idleTimeoutMs?: number;
|
|
103
|
+
maxTurns?: number;
|
|
104
|
+
namespace?: string;
|
|
105
|
+
metadata?: Record<string, unknown>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function boundedJob(name: AgentName, options: BoundedJobOptions): Omit<AgentSpec, 'kind'> {
|
|
109
|
+
return {
|
|
110
|
+
name,
|
|
111
|
+
roles: {
|
|
112
|
+
defaultDelivery: false,
|
|
113
|
+
receivesUntargeted: false,
|
|
114
|
+
receivesGateWakes: false,
|
|
115
|
+
ownsProviderScheduling: false,
|
|
116
|
+
},
|
|
117
|
+
lifetime: {
|
|
118
|
+
kind: 'task',
|
|
119
|
+
...(options.deadlineMs !== undefined ? { deadlineMs: options.deadlineMs } : {}),
|
|
120
|
+
idleTimeoutMs: options.idleTimeoutMs ?? DEFAULT_TASK_IDLE_TIMEOUT_MS,
|
|
121
|
+
...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns } : {}),
|
|
122
|
+
},
|
|
123
|
+
...(options.spawnedBy ? { spawnedBy: options.spawnedBy, onParentEnd: 'orphan' as const } : {}),
|
|
124
|
+
...(options.resultTo ? { resultTo: options.resultTo } : {}),
|
|
125
|
+
namespace: options.namespace ?? `subagent/${name}`,
|
|
126
|
+
...(options.metadata ? { metadata: options.metadata } : {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** A fresh worker: new identity and context, one bounded job, a result route. */
|
|
131
|
+
export function workerSpec(name: AgentName, options: BoundedJobOptions = {}): AgentSpec {
|
|
132
|
+
return { ...boundedJob(name, options), kind: 'worker' };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A task fork: the parent's context at a checkpoint, a private
|
|
137
|
+
* continuation, one bounded job, a return.
|
|
138
|
+
*/
|
|
139
|
+
export function taskForkSpec(
|
|
140
|
+
name: AgentName,
|
|
141
|
+
options: BoundedJobOptions & { parent: AgentName; inherit?: Partial<ContextInheritance> },
|
|
142
|
+
): AgentSpec {
|
|
143
|
+
return {
|
|
144
|
+
...boundedJob(name, { ...options, spawnedBy: options.spawnedBy ?? options.parent }),
|
|
145
|
+
kind: 'task-fork',
|
|
146
|
+
inherit: { from: options.parent, mode: 'copy', ...options.inherit },
|
|
147
|
+
...(options.inherit?.mode === 'shared' ? { selfParticipants: [options.parent] } : {}),
|
|
148
|
+
};
|
|
149
|
+
}
|