@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.
Files changed (104) hide show
  1. package/dist/src/admission/admit.d.ts +90 -0
  2. package/dist/src/admission/admit.d.ts.map +1 -0
  3. package/dist/src/admission/admit.js +43 -0
  4. package/dist/src/admission/admit.js.map +1 -0
  5. package/dist/src/agent.d.ts +21 -0
  6. package/dist/src/agent.d.ts.map +1 -1
  7. package/dist/src/agent.js +43 -6
  8. package/dist/src/agent.js.map +1 -1
  9. package/dist/src/author-presentation.d.ts +50 -0
  10. package/dist/src/author-presentation.d.ts.map +1 -0
  11. package/dist/src/author-presentation.js +58 -0
  12. package/dist/src/author-presentation.js.map +1 -0
  13. package/dist/src/dendrite/index.d.ts +6 -0
  14. package/dist/src/dendrite/index.d.ts.map +1 -0
  15. package/dist/src/dendrite/index.js +3 -0
  16. package/dist/src/dendrite/index.js.map +1 -0
  17. package/dist/src/dendrite/presets.d.ts +60 -0
  18. package/dist/src/dendrite/presets.d.ts.map +1 -0
  19. package/dist/src/dendrite/presets.js +117 -0
  20. package/dist/src/dendrite/presets.js.map +1 -0
  21. package/dist/src/dendrite/registry.d.ts +260 -0
  22. package/dist/src/dendrite/registry.d.ts.map +1 -0
  23. package/dist/src/dendrite/registry.js +742 -0
  24. package/dist/src/dendrite/registry.js.map +1 -0
  25. package/dist/src/dendrite/types.d.ts +392 -0
  26. package/dist/src/dendrite/types.d.ts.map +1 -0
  27. package/dist/src/dendrite/types.js +24 -0
  28. package/dist/src/dendrite/types.js.map +1 -0
  29. package/dist/src/framework.d.ts +684 -33
  30. package/dist/src/framework.d.ts.map +1 -1
  31. package/dist/src/framework.js +2998 -350
  32. package/dist/src/framework.js.map +1 -1
  33. package/dist/src/gate/event-gate.d.ts +12 -4
  34. package/dist/src/gate/event-gate.d.ts.map +1 -1
  35. package/dist/src/gate/event-gate.js +25 -5
  36. package/dist/src/gate/event-gate.js.map +1 -1
  37. package/dist/src/host-presentation.d.ts +4 -0
  38. package/dist/src/host-presentation.d.ts.map +1 -1
  39. package/dist/src/host-presentation.js +7 -3
  40. package/dist/src/host-presentation.js.map +1 -1
  41. package/dist/src/index.d.ts +5 -2
  42. package/dist/src/index.d.ts.map +1 -1
  43. package/dist/src/index.js +6 -1
  44. package/dist/src/index.js.map +1 -1
  45. package/dist/src/mcpl/channel-registry.d.ts +84 -9
  46. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  47. package/dist/src/mcpl/channel-registry.js +136 -58
  48. package/dist/src/mcpl/channel-registry.js.map +1 -1
  49. package/dist/src/mcpl/push-coalescer.d.ts +9 -0
  50. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  51. package/dist/src/mcpl/push-coalescer.js +21 -0
  52. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  53. package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
  54. package/dist/src/mcpl/toolsets-module.js +7 -0
  55. package/dist/src/mcpl/toolsets-module.js.map +1 -1
  56. package/dist/src/notice-tags.d.ts +2 -0
  57. package/dist/src/notice-tags.d.ts.map +1 -1
  58. package/dist/src/notice-tags.js +2 -0
  59. package/dist/src/notice-tags.js.map +1 -1
  60. package/dist/src/silent-heartbeat.d.ts +4 -2
  61. package/dist/src/silent-heartbeat.d.ts.map +1 -1
  62. package/dist/src/silent-heartbeat.js +7 -4
  63. package/dist/src/silent-heartbeat.js.map +1 -1
  64. package/dist/src/stored-injections.d.ts +4 -0
  65. package/dist/src/stored-injections.d.ts.map +1 -1
  66. package/dist/src/stored-injections.js +6 -0
  67. package/dist/src/stored-injections.js.map +1 -1
  68. package/dist/src/tune-out/coordinator.d.ts +379 -9
  69. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  70. package/dist/src/tune-out/coordinator.js +1080 -97
  71. package/dist/src/tune-out/coordinator.js.map +1 -1
  72. package/dist/src/tune-out/tools.d.ts +101 -5
  73. package/dist/src/tune-out/tools.d.ts.map +1 -1
  74. package/dist/src/tune-out/tools.js +55 -18
  75. package/dist/src/tune-out/tools.js.map +1 -1
  76. package/dist/src/types/agent.d.ts +25 -0
  77. package/dist/src/types/agent.d.ts.map +1 -1
  78. package/dist/src/types/framework.d.ts +99 -0
  79. package/dist/src/types/framework.d.ts.map +1 -1
  80. package/dist/src/types/trace.d.ts +136 -0
  81. package/dist/src/types/trace.d.ts.map +1 -1
  82. package/package.json +3 -3
  83. package/src/admission/admit.ts +113 -0
  84. package/src/agent.ts +48 -6
  85. package/src/author-presentation.ts +81 -0
  86. package/src/dendrite/index.ts +32 -0
  87. package/src/dendrite/presets.ts +149 -0
  88. package/src/dendrite/registry.ts +808 -0
  89. package/src/dendrite/types.ts +397 -0
  90. package/src/framework.ts +3239 -369
  91. package/src/gate/event-gate.ts +31 -5
  92. package/src/host-presentation.ts +10 -3
  93. package/src/index.ts +15 -2
  94. package/src/mcpl/channel-registry.ts +170 -60
  95. package/src/mcpl/push-coalescer.ts +19 -0
  96. package/src/mcpl/toolsets-module.ts +7 -0
  97. package/src/notice-tags.ts +2 -0
  98. package/src/silent-heartbeat.ts +7 -4
  99. package/src/stored-injections.ts +7 -0
  100. package/src/tune-out/coordinator.ts +1229 -98
  101. package/src/tune-out/tools.ts +152 -11
  102. package/src/types/agent.ts +28 -0
  103. package/src/types/framework.ts +102 -0
  104. 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
+ }