@animalabs/agent-framework 0.29.0 → 0.31.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 (236) hide show
  1. package/README.md +71 -8
  2. package/dist/src/admission/admit.d.ts +90 -0
  3. package/dist/src/admission/admit.d.ts.map +1 -0
  4. package/dist/src/admission/admit.js +43 -0
  5. package/dist/src/admission/admit.js.map +1 -0
  6. package/dist/src/agent.d.ts +50 -9
  7. package/dist/src/agent.d.ts.map +1 -1
  8. package/dist/src/agent.js +125 -23
  9. package/dist/src/agent.js.map +1 -1
  10. package/dist/src/api/mcp-server-core.d.ts.map +1 -1
  11. package/dist/src/api/mcp-server-core.js +10 -3
  12. package/dist/src/api/mcp-server-core.js.map +1 -1
  13. package/dist/src/author-presentation.d.ts +50 -0
  14. package/dist/src/author-presentation.d.ts.map +1 -0
  15. package/dist/src/author-presentation.js +58 -0
  16. package/dist/src/author-presentation.js.map +1 -0
  17. package/dist/src/code-execution/isolate/render.d.ts.map +1 -1
  18. package/dist/src/code-execution/isolate/render.js +7 -6
  19. package/dist/src/code-execution/isolate/render.js.map +1 -1
  20. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  21. package/dist/src/code-execution/isolate/scripts.js +5 -4
  22. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  23. package/dist/src/dendrite/index.d.ts +6 -0
  24. package/dist/src/dendrite/index.d.ts.map +1 -0
  25. package/dist/src/dendrite/index.js +3 -0
  26. package/dist/src/dendrite/index.js.map +1 -0
  27. package/dist/src/dendrite/presets.d.ts +60 -0
  28. package/dist/src/dendrite/presets.d.ts.map +1 -0
  29. package/dist/src/dendrite/presets.js +117 -0
  30. package/dist/src/dendrite/presets.js.map +1 -0
  31. package/dist/src/dendrite/registry.d.ts +260 -0
  32. package/dist/src/dendrite/registry.d.ts.map +1 -0
  33. package/dist/src/dendrite/registry.js +742 -0
  34. package/dist/src/dendrite/registry.js.map +1 -0
  35. package/dist/src/dendrite/types.d.ts +392 -0
  36. package/dist/src/dendrite/types.d.ts.map +1 -0
  37. package/dist/src/dendrite/types.js +24 -0
  38. package/dist/src/dendrite/types.js.map +1 -0
  39. package/dist/src/framework.d.ts +806 -118
  40. package/dist/src/framework.d.ts.map +1 -1
  41. package/dist/src/framework.js +4180 -1121
  42. package/dist/src/framework.js.map +1 -1
  43. package/dist/src/gate/event-gate.d.ts +36 -21
  44. package/dist/src/gate/event-gate.d.ts.map +1 -1
  45. package/dist/src/gate/event-gate.js +223 -149
  46. package/dist/src/gate/event-gate.js.map +1 -1
  47. package/dist/src/gate/recipe-reconcile.d.ts.map +1 -1
  48. package/dist/src/gate/recipe-reconcile.js +6 -5
  49. package/dist/src/gate/recipe-reconcile.js.map +1 -1
  50. package/dist/src/host-presentation.d.ts +4 -0
  51. package/dist/src/host-presentation.d.ts.map +1 -1
  52. package/dist/src/host-presentation.js +12 -10
  53. package/dist/src/host-presentation.js.map +1 -1
  54. package/dist/src/index.d.ts +6 -2
  55. package/dist/src/index.d.ts.map +1 -1
  56. package/dist/src/index.js +7 -1
  57. package/dist/src/index.js.map +1 -1
  58. package/dist/src/inference-bounds.d.ts +37 -0
  59. package/dist/src/inference-bounds.d.ts.map +1 -0
  60. package/dist/src/inference-bounds.js +84 -0
  61. package/dist/src/inference-bounds.js.map +1 -0
  62. package/dist/src/line-reader.d.ts +7 -1
  63. package/dist/src/line-reader.d.ts.map +1 -1
  64. package/dist/src/line-reader.js +17 -6
  65. package/dist/src/line-reader.js.map +1 -1
  66. package/dist/src/mcpl/channel-registry.d.ts +84 -9
  67. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  68. package/dist/src/mcpl/channel-registry.js +136 -58
  69. package/dist/src/mcpl/channel-registry.js.map +1 -1
  70. package/dist/src/mcpl/errors.d.ts +1 -0
  71. package/dist/src/mcpl/errors.d.ts.map +1 -1
  72. package/dist/src/mcpl/errors.js +3 -0
  73. package/dist/src/mcpl/errors.js.map +1 -1
  74. package/dist/src/mcpl/inbound-source.d.ts +2 -2
  75. package/dist/src/mcpl/inbound-source.js +2 -2
  76. package/dist/src/mcpl/inference-router.d.ts +11 -1
  77. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  78. package/dist/src/mcpl/inference-router.js +65 -9
  79. package/dist/src/mcpl/inference-router.js.map +1 -1
  80. package/dist/src/mcpl/modern-connection.d.ts +10 -7
  81. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  82. package/dist/src/mcpl/modern-connection.js +85 -74
  83. package/dist/src/mcpl/modern-connection.js.map +1 -1
  84. package/dist/src/mcpl/protocol-family.d.ts +1 -1
  85. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  86. package/dist/src/mcpl/protocol-family.js +45 -3
  87. package/dist/src/mcpl/protocol-family.js.map +1 -1
  88. package/dist/src/mcpl/push-coalescer.d.ts +11 -7
  89. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  90. package/dist/src/mcpl/push-coalescer.js +21 -0
  91. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  92. package/dist/src/mcpl/references.d.ts +1 -2
  93. package/dist/src/mcpl/references.d.ts.map +1 -1
  94. package/dist/src/mcpl/references.js +3 -3
  95. package/dist/src/mcpl/references.js.map +1 -1
  96. package/dist/src/mcpl/server-connection.d.ts +14 -0
  97. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  98. package/dist/src/mcpl/server-connection.js +26 -22
  99. package/dist/src/mcpl/server-connection.js.map +1 -1
  100. package/dist/src/mcpl/tool-signature.d.ts +1 -0
  101. package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
  102. package/dist/src/mcpl/tool-signature.js +16 -11
  103. package/dist/src/mcpl/tool-signature.js.map +1 -1
  104. package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
  105. package/dist/src/mcpl/toolsets-module.js +7 -0
  106. package/dist/src/mcpl/toolsets-module.js.map +1 -1
  107. package/dist/src/mcpl/transport.d.ts +44 -21
  108. package/dist/src/mcpl/transport.d.ts.map +1 -1
  109. package/dist/src/mcpl/transport.js +283 -64
  110. package/dist/src/mcpl/transport.js.map +1 -1
  111. package/dist/src/mcpl/types.d.ts +17 -1
  112. package/dist/src/mcpl/types.d.ts.map +1 -1
  113. package/dist/src/mcpl/types.js.map +1 -1
  114. package/dist/src/module-registry.d.ts +32 -4
  115. package/dist/src/module-registry.d.ts.map +1 -1
  116. package/dist/src/module-registry.js +189 -25
  117. package/dist/src/module-registry.js.map +1 -1
  118. package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
  119. package/dist/src/modules/discord/discord-js-client.js +24 -16
  120. package/dist/src/modules/discord/discord-js-client.js.map +1 -1
  121. package/dist/src/modules/sender-name.d.ts +5 -4
  122. package/dist/src/modules/sender-name.d.ts.map +1 -1
  123. package/dist/src/modules/sender-name.js +7 -5
  124. package/dist/src/modules/sender-name.js.map +1 -1
  125. package/dist/src/modules/workspace/disk-agreement.d.ts +16 -8
  126. package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
  127. package/dist/src/modules/workspace/disk-agreement.js +33 -12
  128. package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
  129. package/dist/src/modules/workspace/index.d.ts +6 -3
  130. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  131. package/dist/src/modules/workspace/index.js +61 -15
  132. package/dist/src/modules/workspace/index.js.map +1 -1
  133. package/dist/src/modules/workspace/observe.d.ts +9 -1
  134. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  135. package/dist/src/modules/workspace/observe.js +50 -10
  136. package/dist/src/modules/workspace/observe.js.map +1 -1
  137. package/dist/src/modules/workspace/reconcile.d.ts +11 -1
  138. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  139. package/dist/src/modules/workspace/reconcile.js +19 -8
  140. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  141. package/dist/src/modules/workspace/types.d.ts +8 -0
  142. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  143. package/dist/src/modules/workspace/types.js.map +1 -1
  144. package/dist/src/modules/workspace/watcher.d.ts +7 -0
  145. package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
  146. package/dist/src/modules/workspace/watcher.js +7 -5
  147. package/dist/src/modules/workspace/watcher.js.map +1 -1
  148. package/dist/src/notice-tags.d.ts +2 -0
  149. package/dist/src/notice-tags.d.ts.map +1 -1
  150. package/dist/src/notice-tags.js +2 -0
  151. package/dist/src/notice-tags.js.map +1 -1
  152. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  153. package/dist/src/recovery/discord-awareness-outbox.js +1 -9
  154. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  155. package/dist/src/silent-heartbeat.d.ts +4 -2
  156. package/dist/src/silent-heartbeat.d.ts.map +1 -1
  157. package/dist/src/silent-heartbeat.js +7 -4
  158. package/dist/src/silent-heartbeat.js.map +1 -1
  159. package/dist/src/stored-injections.d.ts +4 -0
  160. package/dist/src/stored-injections.d.ts.map +1 -1
  161. package/dist/src/stored-injections.js +6 -0
  162. package/dist/src/stored-injections.js.map +1 -1
  163. package/dist/src/supplied-text.d.ts +2 -0
  164. package/dist/src/supplied-text.d.ts.map +1 -1
  165. package/dist/src/supplied-text.js +16 -7
  166. package/dist/src/supplied-text.js.map +1 -1
  167. package/dist/src/tool-result-guard.js +1 -1
  168. package/dist/src/tool-result-guard.js.map +1 -1
  169. package/dist/src/tune-out/coordinator.d.ts +368 -16
  170. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  171. package/dist/src/tune-out/coordinator.js +1224 -136
  172. package/dist/src/tune-out/coordinator.js.map +1 -1
  173. package/dist/src/tune-out/tools.d.ts +115 -7
  174. package/dist/src/tune-out/tools.d.ts.map +1 -1
  175. package/dist/src/tune-out/tools.js +55 -18
  176. package/dist/src/tune-out/tools.js.map +1 -1
  177. package/dist/src/types/agent.d.ts +27 -1
  178. package/dist/src/types/agent.d.ts.map +1 -1
  179. package/dist/src/types/framework.d.ts +153 -15
  180. package/dist/src/types/framework.d.ts.map +1 -1
  181. package/dist/src/types/module.d.ts +42 -3
  182. package/dist/src/types/module.d.ts.map +1 -1
  183. package/dist/src/types/trace.d.ts +140 -3
  184. package/dist/src/types/trace.d.ts.map +1 -1
  185. package/package.json +4 -4
  186. package/src/admission/admit.ts +113 -0
  187. package/src/agent.ts +138 -31
  188. package/src/api/mcp-server-core.ts +10 -3
  189. package/src/author-presentation.ts +81 -0
  190. package/src/code-execution/isolate/render.ts +7 -6
  191. package/src/code-execution/isolate/scripts.ts +5 -4
  192. package/src/dendrite/index.ts +32 -0
  193. package/src/dendrite/presets.ts +149 -0
  194. package/src/dendrite/registry.ts +808 -0
  195. package/src/dendrite/types.ts +397 -0
  196. package/src/framework.ts +4344 -1052
  197. package/src/gate/event-gate.ts +251 -150
  198. package/src/gate/recipe-reconcile.ts +7 -5
  199. package/src/host-presentation.ts +14 -10
  200. package/src/index.ts +16 -2
  201. package/src/inference-bounds.ts +111 -0
  202. package/src/line-reader.ts +22 -5
  203. package/src/mcpl/channel-registry.ts +170 -60
  204. package/src/mcpl/errors.ts +4 -0
  205. package/src/mcpl/inbound-source.ts +2 -2
  206. package/src/mcpl/inference-router.ts +67 -10
  207. package/src/mcpl/modern-connection.ts +83 -68
  208. package/src/mcpl/protocol-family.ts +37 -4
  209. package/src/mcpl/push-coalescer.ts +21 -7
  210. package/src/mcpl/references.ts +3 -4
  211. package/src/mcpl/server-connection.ts +30 -21
  212. package/src/mcpl/tool-signature.ts +18 -11
  213. package/src/mcpl/toolsets-module.ts +7 -0
  214. package/src/mcpl/transport.ts +261 -60
  215. package/src/mcpl/types.ts +18 -1
  216. package/src/module-registry.ts +178 -25
  217. package/src/modules/discord/discord-js-client.ts +33 -29
  218. package/src/modules/sender-name.ts +8 -5
  219. package/src/modules/workspace/disk-agreement.ts +39 -15
  220. package/src/modules/workspace/index.ts +65 -14
  221. package/src/modules/workspace/observe.ts +51 -10
  222. package/src/modules/workspace/reconcile.ts +22 -8
  223. package/src/modules/workspace/types.ts +5 -0
  224. package/src/modules/workspace/watcher.ts +8 -6
  225. package/src/notice-tags.ts +2 -0
  226. package/src/recovery/discord-awareness-outbox.ts +1 -11
  227. package/src/silent-heartbeat.ts +7 -4
  228. package/src/stored-injections.ts +7 -0
  229. package/src/supplied-text.ts +17 -6
  230. package/src/tool-result-guard.ts +1 -1
  231. package/src/tune-out/coordinator.ts +1374 -141
  232. package/src/tune-out/tools.ts +166 -13
  233. package/src/types/agent.ts +30 -1
  234. package/src/types/framework.ts +157 -15
  235. package/src/types/module.ts +42 -3
  236. package/src/types/trace.ts +154 -3
@@ -40,6 +40,8 @@
40
40
  * rule with no name is never merged: the merge keys rules by name.
41
41
  */
42
42
 
43
+ import { quotedValue } from '../supplied-text.js';
44
+
43
45
  /** What a start changed, by rule name, for its trace and its log line. */
44
46
  export interface RecipeChanges {
45
47
  added: string[];
@@ -173,11 +175,11 @@ export function reconcileWithRecipe(
173
175
  const agree = (a: unknown, b: unknown): boolean => meaningOf(a) === meaningOf(b);
174
176
  const liveDuplicate = duplicateName(live.rules);
175
177
  if (liveDuplicate !== undefined) {
176
- throw new Error(`gate.json names the rule "${liveDuplicate}" twice`);
178
+ throw new Error(`gate.json names the rule ${quotedValue(liveDuplicate)} twice`);
177
179
  }
178
180
  const recipeDuplicate = duplicateName(recipe.rules);
179
181
  if (recipeDuplicate !== undefined) {
180
- throw new Error(`the recipe names the rule "${recipeDuplicate}" twice`);
182
+ throw new Error(`the recipe names the rule ${quotedValue(recipeDuplicate)} twice`);
181
183
  }
182
184
 
183
185
  const changes = noChanges();
@@ -322,7 +324,7 @@ export function reconcileLine(changes: RecipeChanges): string | null {
322
324
  parts.push(`the recorded recipe couldn't be read (${changes.recordUnreadable}), so this start removed and redefined nothing`);
323
325
  }
324
326
  const list = (label: string, names: string[]) => {
325
- if (names.length > 0) parts.push(`${label} ${names.join(', ')}`);
327
+ if (names.length > 0) parts.push(`${label} ${names.map((name) => quotedValue(name)).join(', ')}`);
326
328
  };
327
329
  list('added', changes.added);
328
330
  list('removed', changes.removed);
@@ -332,13 +334,13 @@ export function reconcileLine(changes: RecipeChanges): string | null {
332
334
  list('kept as edited, though the recipe changed or dropped it:', changes.keptEdited);
333
335
  if (changes.differsFromRecipe.length > 0) {
334
336
  parts.push(
335
- `kept, though the recipe defines it otherwise: ${changes.differsFromRecipe.join(', ')} `
337
+ `kept, though the recipe defines it otherwise: ${changes.differsFromRecipe.map((name) => quotedValue(name)).join(', ')} `
336
338
  + '(remove such a rule, and the next start restores it from the recipe)',
337
339
  );
338
340
  }
339
341
  if (changes.outsideRecipe.length > 0) {
340
342
  parts.push(
341
- `kept, not in the recipe: ${changes.outsideRecipe.join(', ')} `
343
+ `kept, not in the recipe: ${changes.outsideRecipe.map((name) => quotedValue(name)).join(', ')} `
342
344
  + "(a stale one can be removed, and no start brings it back)",
343
345
  );
344
346
  }
@@ -29,7 +29,7 @@
29
29
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
30
30
  import { dirname } from 'node:path';
31
31
  import type { ToolDefinition } from './types/events.js';
32
- import { checkArgs, firstSentence, renderToolDefinition, signature } from './mcpl/tool-signature.js';
32
+ import { checkArgs, renderToolDefinition, toolLine } from './mcpl/tool-signature.js';
33
33
  import { META_TOOL_NAMES } from './mcpl/toolset-host.js';
34
34
 
35
35
  /** The built-in toolset's name, as agents address it (`toolsets--open {toolset: "settings"}`). */
@@ -72,6 +72,8 @@ export interface HostPresentationDeps {
72
72
  isUtility(toolName: string): boolean;
73
73
  /** The resident agents of this host (no forks, ephemeral runs or the subconscious). */
74
74
  residents(): string[];
75
+ /** The agent whose presentation this one is shown: a derived agent that shows its parent's tool list, its parent; anyone else, itself. */
76
+ ownerOf?(agentName: string): string;
75
77
  }
76
78
 
77
79
  interface AgentState {
@@ -198,10 +200,15 @@ export class HostPresentation {
198
200
  return server?.configured === 'closed-tab' && !server.connected;
199
201
  }
200
202
 
203
+ /** The agent whose presentation `agentName` is shown (HostPresentationDeps.ownerOf). */
204
+ ownerOf(agentName: string): string {
205
+ return this.deps.ownerOf?.(agentName) ?? agentName;
206
+ }
207
+
201
208
  /** How a server is presented to an agent now. */
202
209
  presentation(agentName: string, serverId: string): ServerPresentation {
203
210
  if (this.isClosedTab(serverId)) return 'closed-tab';
204
- const chosen = this.doc.agents[agentName]?.servers[serverId];
211
+ const chosen = this.doc.agents[this.ownerOf(agentName)]?.servers[serverId];
205
212
  if (chosen) return chosen;
206
213
  const configured = this.server(serverId)?.configured ?? 'full';
207
214
  return configured === 'closed-tab' ? 'tab' : configured;
@@ -218,7 +225,7 @@ export class HostPresentation {
218
225
  }
219
226
 
220
227
  isOpen(agentName: string, key: string): boolean {
221
- return this.doc.agents[agentName]?.open.includes(key) ?? false;
228
+ return this.doc.agents[this.ownerOf(agentName)]?.open.includes(key) ?? false;
222
229
  }
223
230
 
224
231
  private setOpen(agentName: string, key: string, open: boolean): void {
@@ -267,10 +274,7 @@ export class HostPresentation {
267
274
  private announce(label: string, tools: ToolDefinition[], closeHint: string): string {
268
275
  const lines = [`${label} open: call these with ${META_TOOL_NAMES.act} {tool, args}; they aren't in your tool list.`];
269
276
  if (!tools.length) lines.push(' (none right now)');
270
- for (const t of tools) {
271
- const sentence = firstSentence(t.description);
272
- lines.push(` ${signature(t)}${sentence ? ` — ${sentence}` : ''}`);
273
- }
277
+ for (const t of tools) lines.push(toolLine(t));
274
278
  lines.push(`${closeHint} closes it.`);
275
279
  return lines.join('\n');
276
280
  }
@@ -311,7 +315,7 @@ export class HostPresentation {
311
315
  const open = this.isOpen(agentName, BUILTIN_TOOLSET);
312
316
  return [
313
317
  `${this.builtinLabel} ${open ? 'open' : 'closed'}${open ? '' : ` — ${META_TOOL_NAMES.open} {toolset:"${BUILTIN_TOOLSET}"} opens it`}`,
314
- ...tools.map((t) => ` ${signature(t)}${firstSentence(t.description) ? ` — ${firstSentence(t.description)}` : ''}`),
318
+ ...tools.map((t) => toolLine(t)),
315
319
  ].join('\n');
316
320
  }
317
321
  const { serverId } = which;
@@ -322,7 +326,7 @@ export class HostPresentation {
322
326
  const head = mode === 'full'
323
327
  ? `${this.tabLabel(serverId)} in your tool list${tools.length ? `; its hidden tools (${open ? 'open' : 'closed'}):` : '.'}`
324
328
  : `${this.tabLabel(serverId)} a tab, connected, ${open ? 'open' : `hidden — ${META_TOOL_NAMES.open} {server:"${serverId}"} opens it`}:`;
325
- return [head, ...tools.map((t) => ` ${signature(t)}${firstSentence(t.description) ? ` — ${firstSentence(t.description)}` : ''}`)].join('\n');
329
+ return [head, ...tools.map((t) => toolLine(t))].join('\n');
326
330
  }
327
331
 
328
332
  /** `toolsets--look {tool}` for a host toolset's tool, or null when no host toolset has it. */
@@ -578,7 +582,7 @@ export class HostPresentation {
578
582
  openMarkers(agentName: string): Array<{ marker: string; reminder: () => string; dueWhenAbsent: true }> {
579
583
  if (!this.active(agentName)) return [];
580
584
  const out: Array<{ marker: string; reminder: () => string; dueWhenAbsent: true }> = [];
581
- for (const key of this.doc.agents[agentName]?.open ?? []) {
585
+ for (const key of this.doc.agents[this.ownerOf(agentName)]?.open ?? []) {
582
586
  if (key === BUILTIN_TOOLSET) {
583
587
  out.push({
584
588
  marker: `${this.builtinLabel} open`,
package/src/index.ts CHANGED
@@ -2,9 +2,10 @@
2
2
  export * from './types/index.js';
3
3
 
4
4
  // Core classes
5
- export { AgentFramework, BudgetPreflightError, DefaultErrorPolicy, ProviderWaitNotSleptError, QuiesceBlockedError, ResumeBlockedError } from './framework.js';
6
- export type { RuntimeSettingsPreview, HostModeStatus } from './framework.js';
5
+ export { AgentFramework, AgentStoppedError, BudgetPreflightError, DefaultErrorPolicy, ProviderWaitNotSleptError, QuiesceBlockedError, ResumeBlockedError } from './framework.js';
6
+ export type { RuntimeSettingsPreview, HostModeStatus, DeriveAgentOptions } from './framework.js';
7
7
  export { Agent } from './agent.js';
8
+ export { FLAT_MINIMUM_MS, type InferenceBoundsOptions } from './inference-bounds.js';
8
9
  export { storedInjectionMarkOf, type StoredInjectionMark } from './stored-injections.js';
9
10
  export type { StartStreamResult } from './agent.js';
10
11
  export { ProcessQueueImpl } from './queue.js';
@@ -15,6 +16,9 @@ export { CLOCK_ROW_KIND, resolveClockConfig } from './clock-rows.js';
15
16
  export type { ClockConfig, ClockStamp, ResolvedClockConfig } from './clock-rows.js';
16
17
  export { REFUSAL_REACTIONS, REFUSAL_REACTION_FALLBACK, REFUSAL_REACTION_BASELINE } from './refusal-reactions.js';
17
18
 
19
+ // Dendrite: agent registry, lifecycle and presets
20
+ export * from './dendrite/index.js';
21
+
18
22
  // Built-in modules
19
23
  export * from './modules/index.js';
20
24
 
@@ -196,6 +200,16 @@ export type {
196
200
  } from './recovery/offline-branch.js';
197
201
 
198
202
  export * from "./tool-presentation.js";
203
+
204
+ // Tune-out: the subconscious configuration and the default reader-fork framing.
205
+ export { presentAuthor, authorLine, authorNamed, ANONYMOUS_PARTICIPANT } from './author-presentation.js';
206
+ export {
207
+ defaultReaderFraming,
208
+ type SubconsciousConfig,
209
+ type PersistentReaderConfig,
210
+ type ForkReaderConfig,
211
+ type ReaderFramingContext,
212
+ } from './tune-out/tools.js';
199
213
  export {
200
214
  SILENT_HEARTBEAT_SEPARATOR,
201
215
  silentHeartbeatOf,
@@ -0,0 +1,111 @@
1
+ import { MembraneError } from '@animalabs/membrane';
2
+ import { PROGRESS_BOUNDS, resolveProgressBounds, type ProgressBounds } from '@animalabs/membrane/progress-bounds';
3
+
4
+ /** FrameworkConfig.inferenceBounds: how long the framework lets a model call
5
+ * go without progress. */
6
+ export interface InferenceBoundsOptions {
7
+ /** Membrane's phase bounds for a stream whose adapter reports content
8
+ * (Anthropic, Bedrock), and the values the flat and whole-call bounds
9
+ * derive from: the larger of `beforeContentMs` and `betweenEventsMs`,
10
+ * plus `max_tokens` at `thinkTokensPerSecond`. Each one given replaces
11
+ * membrane's default (PROGRESS_BOUNDS). */
12
+ progressBounds?: Partial<ProgressBounds>;
13
+ /** The least the flat bound on a stream can be, from its last provider
14
+ * event. Default FLAT_MINIMUM_MS. */
15
+ flatMinimumMs?: number;
16
+ }
17
+
18
+ export const FLAT_MINIMUM_MS = 1_862_000;
19
+
20
+ export interface InferenceBounds {
21
+ readonly progressBounds: Readonly<ProgressBounds>;
22
+ readonly flatMinimumMs: number;
23
+ }
24
+
25
+ export function resolveInferenceBounds(option: false | InferenceBoundsOptions | undefined): InferenceBounds | null {
26
+ if (option === false) return null;
27
+ if (option !== undefined && (typeof option !== 'object' || option === null)) {
28
+ throw new TypeError(`[agent-framework] inferenceBounds must be false or an object, got ${option === null ? 'null' : typeof option}`);
29
+ }
30
+ const given = option?.progressBounds;
31
+ if (given !== undefined && (typeof given !== 'object' || given === null)) {
32
+ throw new TypeError(`[agent-framework] inferenceBounds.progressBounds must be an object of bounds, got ${given === null ? 'null' : typeof given}`);
33
+ }
34
+ let progressBounds: ProgressBounds;
35
+ try {
36
+ progressBounds = resolveProgressBounds(given ?? true) ?? { ...PROGRESS_BOUNDS };
37
+ } catch (refused) {
38
+ const why = refused instanceof Error ? refused.message.replace(/^\[membrane\] /, '') : String(refused);
39
+ throw new (refused instanceof TypeError ? TypeError : RangeError)(`[agent-framework] inferenceBounds.${why}`);
40
+ }
41
+ const flatMinimumMs: unknown = option?.flatMinimumMs === undefined ? FLAT_MINIMUM_MS : option.flatMinimumMs;
42
+ if (typeof flatMinimumMs !== 'number' || !Number.isFinite(flatMinimumMs) || flatMinimumMs <= 0) {
43
+ throw new RangeError(`[agent-framework] inferenceBounds.flatMinimumMs must be a finite number above 0, got ${String(flatMinimumMs)}`);
44
+ }
45
+ return Object.freeze({ progressBounds: Object.freeze(progressBounds), flatMinimumMs });
46
+ }
47
+
48
+ function allowanceMs(bounds: Readonly<ProgressBounds>, maxTokens: number | undefined): number {
49
+ return typeof maxTokens === 'number' && Number.isFinite(maxTokens) && maxTokens > 0
50
+ ? Math.ceil(maxTokens / bounds.thinkTokensPerSecond) * 1000
51
+ : 0;
52
+ }
53
+
54
+ function setupMs(bounds: Readonly<ProgressBounds>): number {
55
+ return Math.max(bounds.beforeContentMs, bounds.betweenEventsMs);
56
+ }
57
+
58
+ export function streamBounds(
59
+ bounds: InferenceBounds | null,
60
+ maxTokens: number | undefined,
61
+ ): { progressBounds?: Readonly<ProgressBounds>; progressTimeoutMs?: number } {
62
+ if (bounds === null) return {};
63
+ const phases = bounds.progressBounds;
64
+ return {
65
+ progressBounds: phases,
66
+ progressTimeoutMs: Math.max(setupMs(phases) + allowanceMs(phases, maxTokens), bounds.flatMinimumMs),
67
+ };
68
+ }
69
+
70
+ export function wholeCallMs(bounds: InferenceBounds, maxTokens: number | undefined): number {
71
+ if (!(typeof maxTokens === 'number' && Number.isFinite(maxTokens) && maxTokens > 0)) return Math.max(setupMs(bounds.progressBounds), bounds.flatMinimumMs);
72
+ return setupMs(bounds.progressBounds) + allowanceMs(bounds.progressBounds, maxTokens);
73
+ }
74
+
75
+ // membrane 0.8.0 marks a deadline it reports as run out with this registered
76
+ // symbol (on a sent request, or the Anthropic SDK's timeout, which covers its
77
+ // connect timeout too) and exports no reader or marker for it. The one reader:
78
+ // the deadline class here and the transient-failure check of an exhausted
79
+ // turn's re-arm both use it.
80
+ const DEADLINE_EXPIRY = Symbol.for('membrane.deadlineExpiry');
81
+
82
+ export function isDeadlineExpiry(error: unknown): boolean {
83
+ return typeof error === 'object' && error !== null && (error as Record<symbol, unknown>)[DEADLINE_EXPIRY] === true;
84
+ }
85
+
86
+ /**
87
+ * Names the host setting for the bound a stream cut hit, read from the opening
88
+ * of membrane 0.8.0's message for it (its progress-bounds.ts and
89
+ * providers/utils.ts), so the reason an operator and the agent see names the
90
+ * host's setting for that bound, as the README's table maps them. A message it
91
+ * doesn't know is left as it is.
92
+ */
93
+ export function withHostSetting(message: string): string {
94
+ const setting = message.includes('of a thinking block\'s start') ? 'progressBounds.thinkTokensPerSecond'
95
+ : message.startsWith('No content within ') ? 'progressBounds.beforeContentMs'
96
+ : message.startsWith('No provider event within ') ? 'progressBounds.betweenEventsMs'
97
+ : message.startsWith('No provider progress within ') ? 'flatMinimumMs'
98
+ : undefined;
99
+ return setting === undefined ? message : `${message} (the host's setting for this bound: \`inferenceBounds.${setting}\`)`;
100
+ }
101
+
102
+ export function deadlineCut(message: string | undefined): MembraneError {
103
+ const error = new MembraneError({
104
+ type: 'timeout',
105
+ retryable: false,
106
+ message: message === undefined ? 'The provider made no progress within the call\'s bound' : withHostSetting(message),
107
+ rawError: undefined,
108
+ });
109
+ Object.defineProperty(error, DEADLINE_EXPIRY, { value: true });
110
+ return error;
111
+ }
@@ -4,12 +4,26 @@ import { StringDecoder } from 'node:string_decoder';
4
4
  const LF = 0x0a;
5
5
  const CR = 0x0d;
6
6
 
7
- export function readLines(input: Readable, onLine: (line: string) => void, onEnd?: () => void): () => void {
7
+ export const MAX_LINE_CHARS = 100 * 1024 * 1024;
8
+
9
+ export interface LineHandlers {
10
+ line: (line: string) => void;
11
+ end?: () => void;
12
+ tooLong: (maxChars: number) => void;
13
+ }
14
+
15
+ export function readLines(input: Readable, on: LineHandlers, maxChars = MAX_LINE_CHARS): () => void {
8
16
  const decoder = new StringDecoder('utf8');
9
17
  let partial = '';
10
18
  let lineEndedAtCr = false;
11
19
  let stopped = false;
12
20
 
21
+ const refuse = () => {
22
+ stop();
23
+ partial = '';
24
+ on.tooLong(maxChars);
25
+ };
26
+
13
27
  const take = (text: string) => {
14
28
  let from = 0;
15
29
  if (lineEndedAtCr && text.length > 0) {
@@ -19,6 +33,7 @@ export function readLines(input: Readable, onLine: (line: string) => void, onEnd
19
33
  for (let i = from; i < text.length && !stopped; i++) {
20
34
  const code = text.charCodeAt(i);
21
35
  if (code !== LF && code !== CR) continue;
36
+ if (partial.length + (i - from) > maxChars) return refuse();
22
37
  const line = partial + text.slice(from, i);
23
38
  partial = '';
24
39
  if (code === CR) {
@@ -26,9 +41,11 @@ export function readLines(input: Readable, onLine: (line: string) => void, onEnd
26
41
  else if (text.charCodeAt(i + 1) === LF) i++;
27
42
  }
28
43
  from = i + 1;
29
- onLine(line);
44
+ on.line(line);
30
45
  }
31
- if (!stopped) partial += text.slice(from);
46
+ if (stopped) return;
47
+ if (partial.length + (text.length - from) > maxChars) return refuse();
48
+ partial += text.slice(from);
32
49
  };
33
50
 
34
51
  const onData = (chunk: Buffer | string) => take(decoder.write(chunk));
@@ -37,11 +54,11 @@ export function readLines(input: Readable, onLine: (line: string) => void, onEnd
37
54
  if (!stopped && partial.length > 0) {
38
55
  const last = partial;
39
56
  partial = '';
40
- onLine(last);
57
+ on.line(last);
41
58
  }
42
59
  if (stopped) return;
43
60
  stop();
44
- onEnd?.();
61
+ on.end?.();
45
62
  };
46
63
  const stop = () => {
47
64
  stopped = true;