@animalabs/agent-framework 0.26.1 → 0.27.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 (276) hide show
  1. package/README.md +57 -7
  2. package/dist/src/agent.d.ts +263 -22
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +531 -54
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/server.d.ts.map +1 -1
  7. package/dist/src/api/server.js +1 -2
  8. package/dist/src/api/server.js.map +1 -1
  9. package/dist/src/auto-continue.d.ts +33 -0
  10. package/dist/src/auto-continue.d.ts.map +1 -0
  11. package/dist/src/auto-continue.js +35 -0
  12. package/dist/src/auto-continue.js.map +1 -0
  13. package/dist/src/code-execution/isolate/scripts.d.ts +11 -0
  14. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  15. package/dist/src/code-execution/isolate/scripts.js +5 -1
  16. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  17. package/dist/src/framework.d.ts +829 -156
  18. package/dist/src/framework.d.ts.map +1 -1
  19. package/dist/src/framework.js +3906 -1008
  20. package/dist/src/framework.js.map +1 -1
  21. package/dist/src/gate/event-gate.d.ts +309 -46
  22. package/dist/src/gate/event-gate.d.ts.map +1 -1
  23. package/dist/src/gate/event-gate.js +2117 -395
  24. package/dist/src/gate/event-gate.js.map +1 -1
  25. package/dist/src/gate/index.d.ts +2 -2
  26. package/dist/src/gate/index.d.ts.map +1 -1
  27. package/dist/src/gate/index.js +1 -1
  28. package/dist/src/gate/index.js.map +1 -1
  29. package/dist/src/gate/recipe-reconcile.d.ts +103 -0
  30. package/dist/src/gate/recipe-reconcile.d.ts.map +1 -0
  31. package/dist/src/gate/recipe-reconcile.js +325 -0
  32. package/dist/src/gate/recipe-reconcile.js.map +1 -0
  33. package/dist/src/gate/types.d.ts +68 -8
  34. package/dist/src/gate/types.d.ts.map +1 -1
  35. package/dist/src/index.d.ts +6 -4
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/dist/src/index.js +5 -2
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/src/mcpl/channel-registry.d.ts +433 -113
  40. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  41. package/dist/src/mcpl/channel-registry.js +1152 -434
  42. package/dist/src/mcpl/channel-registry.js.map +1 -1
  43. package/dist/src/mcpl/conversation-router.d.ts +16 -8
  44. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  45. package/dist/src/mcpl/conversation-router.js +29 -15
  46. package/dist/src/mcpl/conversation-router.js.map +1 -1
  47. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  48. package/dist/src/mcpl/convert-block.js +14 -2
  49. package/dist/src/mcpl/convert-block.js.map +1 -1
  50. package/dist/src/mcpl/hook-orchestrator.d.ts +7 -1
  51. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  52. package/dist/src/mcpl/hook-orchestrator.js +32 -7
  53. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  54. package/dist/src/mcpl/index.d.ts +5 -1
  55. package/dist/src/mcpl/index.d.ts.map +1 -1
  56. package/dist/src/mcpl/index.js +4 -0
  57. package/dist/src/mcpl/index.js.map +1 -1
  58. package/dist/src/mcpl/inference-router.d.ts +14 -2
  59. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  60. package/dist/src/mcpl/inference-router.js +73 -28
  61. package/dist/src/mcpl/inference-router.js.map +1 -1
  62. package/dist/src/mcpl/prose-grammar.d.ts +4 -0
  63. package/dist/src/mcpl/prose-grammar.d.ts.map +1 -1
  64. package/dist/src/mcpl/prose-grammar.js +6 -0
  65. package/dist/src/mcpl/prose-grammar.js.map +1 -1
  66. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  67. package/dist/src/mcpl/protocol-family.js +3 -0
  68. package/dist/src/mcpl/protocol-family.js.map +1 -1
  69. package/dist/src/mcpl/push-coalescer.d.ts +20 -2
  70. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  71. package/dist/src/mcpl/push-coalescer.js +33 -11
  72. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  73. package/dist/src/mcpl/reference-fetcher.d.ts +63 -42
  74. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -1
  75. package/dist/src/mcpl/reference-fetcher.js +177 -145
  76. package/dist/src/mcpl/reference-fetcher.js.map +1 -1
  77. package/dist/src/mcpl/references.d.ts +380 -46
  78. package/dist/src/mcpl/references.d.ts.map +1 -1
  79. package/dist/src/mcpl/references.js +737 -78
  80. package/dist/src/mcpl/references.js.map +1 -1
  81. package/dist/src/mcpl/server-connection.d.ts +82 -3
  82. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  83. package/dist/src/mcpl/server-connection.js +132 -7
  84. package/dist/src/mcpl/server-connection.js.map +1 -1
  85. package/dist/src/mcpl/tool-lifecycle.d.ts +11 -0
  86. package/dist/src/mcpl/tool-lifecycle.d.ts.map +1 -1
  87. package/dist/src/mcpl/tool-lifecycle.js +21 -0
  88. package/dist/src/mcpl/tool-lifecycle.js.map +1 -1
  89. package/dist/src/mcpl/tool-presence.d.ts +259 -0
  90. package/dist/src/mcpl/tool-presence.d.ts.map +1 -0
  91. package/dist/src/mcpl/tool-presence.js +628 -0
  92. package/dist/src/mcpl/tool-presence.js.map +1 -0
  93. package/dist/src/mcpl/tool-result-normalize.d.ts +9 -3
  94. package/dist/src/mcpl/tool-result-normalize.d.ts.map +1 -1
  95. package/dist/src/mcpl/tool-result-normalize.js +14 -6
  96. package/dist/src/mcpl/tool-result-normalize.js.map +1 -1
  97. package/dist/src/mcpl/tool-signature.d.ts +39 -0
  98. package/dist/src/mcpl/tool-signature.d.ts.map +1 -0
  99. package/dist/src/mcpl/tool-signature.js +292 -0
  100. package/dist/src/mcpl/tool-signature.js.map +1 -0
  101. package/dist/src/mcpl/toolset-host.d.ts +200 -0
  102. package/dist/src/mcpl/toolset-host.d.ts.map +1 -0
  103. package/dist/src/mcpl/toolset-host.js +621 -0
  104. package/dist/src/mcpl/toolset-host.js.map +1 -0
  105. package/dist/src/mcpl/toolsets-module.d.ts +80 -0
  106. package/dist/src/mcpl/toolsets-module.d.ts.map +1 -0
  107. package/dist/src/mcpl/toolsets-module.js +431 -0
  108. package/dist/src/mcpl/toolsets-module.js.map +1 -0
  109. package/dist/src/mcpl/transport.d.ts +2 -2
  110. package/dist/src/mcpl/transport.d.ts.map +1 -1
  111. package/dist/src/mcpl/transport.js +3 -2
  112. package/dist/src/mcpl/transport.js.map +1 -1
  113. package/dist/src/mcpl/types.d.ts +21 -1
  114. package/dist/src/mcpl/types.d.ts.map +1 -1
  115. package/dist/src/mcpl/types.js.map +1 -1
  116. package/dist/src/mcpl/visible-content.d.ts +7 -9
  117. package/dist/src/mcpl/visible-content.d.ts.map +1 -1
  118. package/dist/src/mcpl/visible-content.js +21 -9
  119. package/dist/src/mcpl/visible-content.js.map +1 -1
  120. package/dist/src/module-registry.d.ts +23 -8
  121. package/dist/src/module-registry.d.ts.map +1 -1
  122. package/dist/src/module-registry.js +68 -15
  123. package/dist/src/module-registry.js.map +1 -1
  124. package/dist/src/modules/discord/index.js +1 -1
  125. package/dist/src/modules/discord/index.js.map +1 -1
  126. package/dist/src/modules/history/index.d.ts +2 -1
  127. package/dist/src/modules/history/index.d.ts.map +1 -1
  128. package/dist/src/modules/history/index.js +51 -17
  129. package/dist/src/modules/history/index.js.map +1 -1
  130. package/dist/src/modules/history/semantic.d.ts +108 -6
  131. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  132. package/dist/src/modules/history/semantic.js +273 -57
  133. package/dist/src/modules/history/semantic.js.map +1 -1
  134. package/dist/src/modules/workspace/direct-write.d.ts +181 -0
  135. package/dist/src/modules/workspace/direct-write.d.ts.map +1 -0
  136. package/dist/src/modules/workspace/direct-write.js +384 -0
  137. package/dist/src/modules/workspace/direct-write.js.map +1 -0
  138. package/dist/src/modules/workspace/edit-text.d.ts +26 -12
  139. package/dist/src/modules/workspace/edit-text.d.ts.map +1 -1
  140. package/dist/src/modules/workspace/edit-text.js +58 -28
  141. package/dist/src/modules/workspace/edit-text.js.map +1 -1
  142. package/dist/src/modules/workspace/effects.d.ts +10 -0
  143. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  144. package/dist/src/modules/workspace/effects.js +1 -1
  145. package/dist/src/modules/workspace/effects.js.map +1 -1
  146. package/dist/src/modules/workspace/index.d.ts +246 -27
  147. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  148. package/dist/src/modules/workspace/index.js +1216 -236
  149. package/dist/src/modules/workspace/index.js.map +1 -1
  150. package/dist/src/modules/workspace/read-page.d.ts +41 -0
  151. package/dist/src/modules/workspace/read-page.d.ts.map +1 -0
  152. package/dist/src/modules/workspace/read-page.js +213 -0
  153. package/dist/src/modules/workspace/read-page.js.map +1 -0
  154. package/dist/src/modules/workspace/result-text.d.ts +67 -0
  155. package/dist/src/modules/workspace/result-text.d.ts.map +1 -0
  156. package/dist/src/modules/workspace/result-text.js +94 -0
  157. package/dist/src/modules/workspace/result-text.js.map +1 -0
  158. package/dist/src/modules/workspace/text.d.ts +29 -12
  159. package/dist/src/modules/workspace/text.d.ts.map +1 -1
  160. package/dist/src/modules/workspace/text.js +35 -33
  161. package/dist/src/modules/workspace/text.js.map +1 -1
  162. package/dist/src/modules/workspace/types.d.ts +41 -17
  163. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  164. package/dist/src/modules/workspace/types.js.map +1 -1
  165. package/dist/src/sent-rows.d.ts +59 -0
  166. package/dist/src/sent-rows.d.ts.map +1 -0
  167. package/dist/src/sent-rows.js +145 -0
  168. package/dist/src/sent-rows.js.map +1 -0
  169. package/dist/src/silent-heartbeat.d.ts +17 -10
  170. package/dist/src/silent-heartbeat.d.ts.map +1 -1
  171. package/dist/src/silent-heartbeat.js.map +1 -1
  172. package/dist/src/stable-hash.d.ts +3 -0
  173. package/dist/src/stable-hash.d.ts.map +1 -0
  174. package/dist/src/stable-hash.js +15 -0
  175. package/dist/src/stable-hash.js.map +1 -0
  176. package/dist/src/stored-injections.d.ts +99 -0
  177. package/dist/src/stored-injections.d.ts.map +1 -0
  178. package/dist/src/stored-injections.js +106 -0
  179. package/dist/src/stored-injections.js.map +1 -0
  180. package/dist/src/text-as-stored.d.ts +4 -3
  181. package/dist/src/text-as-stored.d.ts.map +1 -1
  182. package/dist/src/text-as-stored.js +4 -3
  183. package/dist/src/text-as-stored.js.map +1 -1
  184. package/dist/src/thinking-drops.d.ts +81 -0
  185. package/dist/src/thinking-drops.d.ts.map +1 -0
  186. package/dist/src/thinking-drops.js +103 -0
  187. package/dist/src/thinking-drops.js.map +1 -0
  188. package/dist/src/tool-image-ledger.d.ts +9 -8
  189. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  190. package/dist/src/tool-image-ledger.js +9 -8
  191. package/dist/src/tool-image-ledger.js.map +1 -1
  192. package/dist/src/tool-presentation.d.ts.map +1 -1
  193. package/dist/src/tool-presentation.js +3 -1
  194. package/dist/src/tool-presentation.js.map +1 -1
  195. package/dist/src/tool-result-guard.d.ts +4 -2
  196. package/dist/src/tool-result-guard.d.ts.map +1 -1
  197. package/dist/src/tool-result-guard.js +21 -6
  198. package/dist/src/tool-result-guard.js.map +1 -1
  199. package/dist/src/tool-result-history.d.ts +186 -21
  200. package/dist/src/tool-result-history.d.ts.map +1 -1
  201. package/dist/src/tool-result-history.js +428 -89
  202. package/dist/src/tool-result-history.js.map +1 -1
  203. package/dist/src/tune-out/coordinator.d.ts +17 -9
  204. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  205. package/dist/src/tune-out/coordinator.js +84 -37
  206. package/dist/src/tune-out/coordinator.js.map +1 -1
  207. package/dist/src/types/agent.d.ts +186 -7
  208. package/dist/src/types/agent.d.ts.map +1 -1
  209. package/dist/src/types/events.d.ts +19 -2
  210. package/dist/src/types/events.d.ts.map +1 -1
  211. package/dist/src/types/framework.d.ts +24 -3
  212. package/dist/src/types/framework.d.ts.map +1 -1
  213. package/dist/src/types/module.d.ts +90 -7
  214. package/dist/src/types/module.d.ts.map +1 -1
  215. package/dist/src/types/trace.d.ts +57 -2
  216. package/dist/src/types/trace.d.ts.map +1 -1
  217. package/package.json +4 -3
  218. package/src/agent.ts +593 -60
  219. package/src/api/server.ts +1 -2
  220. package/src/auto-continue.ts +42 -0
  221. package/src/code-execution/isolate/scripts.ts +11 -3
  222. package/src/framework.ts +4004 -1026
  223. package/src/gate/event-gate.ts +2315 -389
  224. package/src/gate/index.ts +2 -1
  225. package/src/gate/recipe-reconcile.ts +355 -0
  226. package/src/gate/types.ts +70 -8
  227. package/src/index.ts +7 -4
  228. package/src/mcpl/channel-registry.ts +1220 -467
  229. package/src/mcpl/conversation-router.ts +34 -16
  230. package/src/mcpl/convert-block.ts +14 -1
  231. package/src/mcpl/hook-orchestrator.ts +33 -6
  232. package/src/mcpl/index.ts +6 -1
  233. package/src/mcpl/inference-router.ts +79 -34
  234. package/src/mcpl/prose-grammar.ts +7 -0
  235. package/src/mcpl/protocol-family.ts +2 -0
  236. package/src/mcpl/push-coalescer.ts +51 -13
  237. package/src/mcpl/reference-fetcher.ts +201 -162
  238. package/src/mcpl/references.ts +852 -78
  239. package/src/mcpl/server-connection.ts +145 -7
  240. package/src/mcpl/tool-lifecycle.ts +21 -0
  241. package/src/mcpl/tool-presence.ts +650 -0
  242. package/src/mcpl/tool-result-normalize.ts +18 -6
  243. package/src/mcpl/tool-signature.ts +285 -0
  244. package/src/mcpl/toolset-host.ts +637 -0
  245. package/src/mcpl/toolsets-module.ts +451 -0
  246. package/src/mcpl/transport.ts +3 -2
  247. package/src/mcpl/types.ts +22 -1
  248. package/src/mcpl/visible-content.ts +22 -9
  249. package/src/module-registry.ts +77 -24
  250. package/src/modules/discord/index.ts +1 -1
  251. package/src/modules/history/index.ts +48 -18
  252. package/src/modules/history/semantic.ts +308 -54
  253. package/src/modules/workspace/direct-write.ts +425 -0
  254. package/src/modules/workspace/edit-text.ts +82 -35
  255. package/src/modules/workspace/effects.ts +1 -1
  256. package/src/modules/workspace/index.ts +1274 -241
  257. package/src/modules/workspace/read-page.ts +222 -0
  258. package/src/modules/workspace/result-text.ts +119 -0
  259. package/src/modules/workspace/text.ts +58 -32
  260. package/src/modules/workspace/types.ts +41 -17
  261. package/src/sent-rows.ts +162 -0
  262. package/src/silent-heartbeat.ts +17 -10
  263. package/src/stable-hash.ts +14 -0
  264. package/src/stored-injections.ts +131 -0
  265. package/src/text-as-stored.ts +4 -3
  266. package/src/thinking-drops.ts +141 -0
  267. package/src/tool-image-ledger.ts +9 -8
  268. package/src/tool-presentation.ts +3 -1
  269. package/src/tool-result-guard.ts +19 -6
  270. package/src/tool-result-history.ts +488 -88
  271. package/src/tune-out/coordinator.ts +80 -37
  272. package/src/types/agent.ts +189 -7
  273. package/src/types/events.ts +19 -2
  274. package/src/types/framework.ts +21 -3
  275. package/src/types/module.ts +86 -7
  276. package/src/types/trace.ts +58 -2
@@ -18,7 +18,7 @@
18
18
  import { randomUUID } from 'node:crypto';
19
19
  import type { ContentBlock } from '@animalabs/membrane';
20
20
  import type { StoredMessage } from '@animalabs/context-manager';
21
- import type { ChannelRegistry, TuneOutParams } from '../mcpl/channel-registry.js';
21
+ import type { ChannelRef, ChannelRegistry, TuneOutParams } from '../mcpl/channel-registry.js';
22
22
  import type { McplServerRegistry } from '../mcpl/server-registry.js';
23
23
  import { CapabilityGrant } from '../mcpl/capability-grant.js';
24
24
  import { armAfter, armAt } from '../deadline-timer.js';
@@ -45,7 +45,7 @@ const CUT_MARK = " [cut to fit; the whole message is in the channel's history]";
45
45
  * its transport is now where the repeat tried to open it again (`transport`,
46
46
  * from the framework's dispatch), then how to change the settings.
47
47
  */
48
- export function alreadyTunedOutText(channelId: string, p: TuneOutParams, transport?: string): string {
48
+ export function alreadyTunedOutText(shown: string, p: TuneOutParams, transport?: string): string {
49
49
  // A duration so long its end is past what a Date can hold has no time to show.
50
50
  const expires = p.expiresAtMs !== undefined ? new Date(p.expiresAtMs) : undefined;
51
51
  const until = expires && Number.isFinite(expires.getTime())
@@ -53,7 +53,7 @@ export function alreadyTunedOutText(channelId: string, p: TuneOutParams, transpo
53
53
  : '';
54
54
  // A thrown error's words may end without a stop.
55
55
  const now = transport === undefined ? '' : /[.!?]$/.test(transport) ? `${transport} ` : `${transport}. `;
56
- return `Nothing changed${transport !== undefined ? ' in the tune-out' : ''}: ${JSON.stringify(channelId)} is already `
56
+ return `Nothing changed${transport !== undefined ? ' in the tune-out' : ''}: ${JSON.stringify(shown)} is already `
57
57
  + `tuned out, with cadenceSeconds ${p.cadenceSeconds}, backlogCap ${p.backlogCap} and maxWakes ${p.maxWakes}${until}. `
58
58
  + now
59
59
  + 'To change those, cancel it first with tune_out mode "cancel", which delivers its backlog to you now, then call '
@@ -113,7 +113,7 @@ export interface TuneOutFrameworkHooks {
113
113
  /** Move the subconscious window anchor (never forward past active epochs). */
114
114
  setSubconsciousAnchor: (sequence: number) => void;
115
115
  /** True when a channel is bound to a conversation-fork agent. */
116
- isForkBound: (channelId: string) => boolean;
116
+ isForkBound: (channel: ChannelRef) => boolean;
117
117
  /** Gate privileged-users list (#77: privileged authors wake, like mentions). */
118
118
  isPrivilegedAuthor: (authorId: string | null | undefined) => boolean;
119
119
  /** speak_in_channel master switch (recipe-side, canary-gated). */
@@ -144,6 +144,15 @@ export class TuneOutCoordinator {
144
144
  return `${serverId}\0${channelId}`;
145
145
  }
146
146
 
147
+ /** A channel as the resident and the subconscious are shown it
148
+ * (ChannelRegistry.nameShown): its id, or, where another server registers
149
+ * the same id, its server-qualified name, which tune_out and
150
+ * cancel_tuneout take back; or, for a channel no string picks alone, its
151
+ * id and its server. */
152
+ private shown(serverId: string, channelId: string): string {
153
+ return this.channelRegistry.nameShown({ serverId, channelId });
154
+ }
155
+
147
156
  /** Restart cadence + expiry timers and re-anchor after a process restart. */
148
157
  resumeActiveEpochs(): void {
149
158
  let oldestAnchor: number | null = null;
@@ -232,17 +241,17 @@ export class TuneOutCoordinator {
232
241
  + 'whoever runs this host.',
233
242
  };
234
243
  }
235
- if (this.hooks.isForkBound(channelId)) {
244
+ if (this.hooks.isForkBound({ serverId, channelId })) {
236
245
  return {
237
246
  ok: false,
238
- error: `Nothing changed: a conversation is bound to ${JSON.stringify(channelId)}, so its messages go to that `
247
+ error: `Nothing changed: a conversation is bound to ${JSON.stringify(this.shown(serverId, channelId))}, so its messages go to that `
239
248
  + "conversation, and tune_out won't divert a channel while one is. Calling tune_out on it again won't change "
240
249
  + 'that until the conversation ends, which happens only once the channel has been quiet for a while. Tune '
241
250
  + 'out a different channel, or ask whoever runs this host if this one should be diverted.',
242
251
  };
243
252
  }
244
253
  const current = this.channelRegistry.getTuneOutState(serverId, channelId);
245
- if (current) return { ok: false, error: alreadyTunedOutText(channelId, current.params), alreadyTunedOut: true };
254
+ if (current) return { ok: false, error: alreadyTunedOutText(this.shown(serverId, channelId), current.params), alreadyTunedOut: true };
246
255
  // A tool's input reaches here as the model wrote it, and the store can't
247
256
  // keep a NaN or an Infinity: its append would throw after the registry
248
257
  // had marked the channel tuned out. So each option is read as a number
@@ -328,7 +337,7 @@ export class TuneOutCoordinator {
328
337
  const ended = this.channelRegistry.cancelTuneOut(serverId, channelId, 'open', source);
329
338
  if (!ended) {
330
339
  // Its reader is the resident's tune_out: cancel_tuneout finds the channel first.
331
- return { ok: false, error: this.notTunedOut(channelId, 'you') };
340
+ return { ok: false, error: this.notTunedOut(this.shown(serverId, channelId), 'you') };
332
341
  }
333
342
  this.clearTimers(serverId, channelId);
334
343
  this.divertedSinceInvocation.delete(this.key(serverId, channelId));
@@ -359,10 +368,12 @@ export class TuneOutCoordinator {
359
368
  .join('\n');
360
369
  return `${authorOf(m)}: ${text}`;
361
370
  };
362
- const lifecycle = `[Tune-out: ${channelId} cancelled — ${reason}, ${ended.wakeCount} wake${ended.wakeCount === 1 ? '' : 's'}]`;
371
+ // The channel as the resident and the subconscious are shown it.
372
+ const name = this.shown(serverId, channelId);
373
+ const lifecycle = `[Tune-out: ${name} cancelled — ${reason}, ${ended.wakeCount} wake${ended.wakeCount === 1 ? '' : 's'}]`;
363
374
  const render = (lines: string[], truncated: number): string => {
364
375
  const attrs = [
365
- `channel="${channelId}"`,
376
+ `channel="${name}"`,
366
377
  `messages=${backlog.length}`,
367
378
  ...(truncated > 0 ? [`truncated=${truncated} (oldest; fetch_history covers the rest)`] : []),
368
379
  ];
@@ -441,7 +452,9 @@ export class TuneOutCoordinator {
441
452
  this.hooks.addMessage(
442
453
  subName,
443
454
  [{ type: 'text', text: subconsciousNote }],
444
- { kind: 'subconscious-note', channelId, epochId },
455
+ // With its server: a conversational row whose channel the framework
456
+ // reads (repinChannel), where another server's channel can share the id.
457
+ { kind: 'subconscious-note', channelId, serverId, epochId },
445
458
  );
446
459
  }
447
460
  const primary = this.hooks.primaryName();
@@ -467,7 +480,7 @@ export class TuneOutCoordinator {
467
480
  : '';
468
481
  this.hooks.addMessage(
469
482
  'user',
470
- [{ type: 'text', text: `[Tune-out cancelled: ${channelId} — ${reason}.${truncNote}${heldNote}]` }],
483
+ [{ type: 'text', text: `[Tune-out cancelled: ${this.shown(serverId, channelId)} — ${reason}.${truncNote}${heldNote}]` }],
471
484
  { system: true, kind: 'tune-out-cancel-report', channelId },
472
485
  subName,
473
486
  );
@@ -481,7 +494,7 @@ export class TuneOutCoordinator {
481
494
  subName,
482
495
  );
483
496
  }
484
- this.hooks.requestInference(subName, `tune-out cancelled: ${channelId}`, 'tune-out');
497
+ this.hooks.requestInference(subName, `tune-out cancelled: ${name}`, 'tune-out');
485
498
  }
486
499
  this.hooks.emitTrace({ type: 'tune-out:cancelled', serverId, channelId, epochId, reason, wakeCount: ended.wakeCount });
487
500
  return { ok: true };
@@ -649,16 +662,17 @@ export class TuneOutCoordinator {
649
662
  if (!subName) return;
650
663
  const key = this.key(serverId, channelId);
651
664
 
665
+ const shown = this.shown(serverId, channelId);
652
666
  const notice = trigger === 'wake'
653
- ? `[Tune-out wake: ${channelId} — ${count} addressed message${count === 1 ? '' : 's'}]`
654
- : `[Tune-out cadence: ${channelId} — ${count} diverted message${count === 1 ? '' : 's'} since your last look]`;
667
+ ? `[Tune-out wake: ${shown} — ${count} addressed message${count === 1 ? '' : 's'}]`
668
+ : `[Tune-out cadence: ${shown} — ${count} diverted message${count === 1 ? '' : 's'} since your last look]`;
655
669
  this.hooks.addMessage(
656
670
  'user',
657
671
  [{ type: 'text', text: notice }],
658
672
  { system: true, kind: `tune-out-${trigger}`, channelId },
659
673
  subName,
660
674
  );
661
- this.hooks.requestInference(subName, `tune-out ${trigger}: ${channelId}`, 'tune-out');
675
+ this.hooks.requestInference(subName, `tune-out ${trigger}: ${shown}`, 'tune-out');
662
676
  // Counted from here only once the turn has been asked for, so a turn that
663
677
  // throws leaves its messages for the next period's turn.
664
678
  this.divertedSinceInvocation.set(key, 0);
@@ -740,6 +754,8 @@ export class TuneOutCoordinator {
740
754
  async handleSubconsciousTool(
741
755
  name: string,
742
756
  input: Record<string, unknown>,
757
+ /** The calling turn's own channels, for a call made inside it (the framework's callOwnChannels). */
758
+ own: readonly ChannelRef[] = [],
743
759
  ): Promise<{ success: boolean; data?: unknown; error?: string; isError?: boolean }> {
744
760
  switch (name) {
745
761
  case 'deliver_summary': {
@@ -755,19 +771,48 @@ export class TuneOutCoordinator {
755
771
  }
756
772
  const subName = this.hooks.subconsciousName();
757
773
  if (!subName) return { success: false, error: 'subconscious not configured', isError: true };
758
- // Verbatim, under its own participant name, into the resident's window.
774
+ // Verbatim, under its own participant name, into the resident's window,
775
+ // tagged with the channel the name means and its server, since the
776
+ // framework reads a conversational row's channel (repinChannel). The
777
+ // name means what it means to every channel tool: inside the calling
778
+ // turn, the id of one of the turn's own channels means that channel,
779
+ // refused where its server no longer registers it or the turn has
780
+ // channels with that id on two servers (ownChannelFor); otherwise a
781
+ // name several registered channels answer to is refused with them
782
+ // (sharedNameRefusalFor). So no row is left for a re-pin to read as
783
+ // another server's channel. A name no registered channel answers to
784
+ // is kept as given, with no server, which a re-pin reads by name when
785
+ // it weighs the row, as it reads any message without its server.
786
+ const mine = this.channelRegistry.ownChannelFor(channelId, own, 'deliver');
787
+ if (mine && 'error' in mine) return { success: false, error: mine.error, isError: true };
788
+ const shared = mine ? undefined : this.channelRegistry.sharedNameRefusalFor(channelId, 'nothing was delivered');
789
+ if (shared) return { success: false, error: shared, isError: true };
790
+ const [named] = mine ? [mine.channel] : this.channelRegistry.channelsNamed(channelId);
759
791
  this.hooks.addMessage(subName, [{ type: 'text', text }], {
760
792
  kind: 'subconscious-summary',
761
- channelId,
793
+ ...(named ? { channelId: named.channelId, serverId: named.serverId } : { channelId }),
762
794
  });
763
795
  return { success: true, data: { delivered: true } };
764
796
  }
765
797
  case 'cancel_tuneout': {
766
- const channelId = String(input.channelId ?? '');
767
- const located = this.findByChannel(channelId);
768
- if (!located) return { success: false, error: this.notTunedOut(channelId, 'the resident'), isError: true };
798
+ const name = String(input.channelId ?? '');
799
+ // The name means what it means to every channel tool, the
800
+ // resident's tune_out included: inside the calling turn, the id of
801
+ // one of the turn's own channels means that channel (ownChannelFor);
802
+ // otherwise a name several registered channels answer to is refused
803
+ // (channelsNamed), whichever of them is tuned out, never taken to be
804
+ // the tuned-out one.
805
+ const mine = this.channelRegistry.ownChannelFor(name, own);
806
+ if (mine && 'error' in mine) return { success: false, error: mine.error, isError: true };
807
+ const named = mine ? [mine.channel] : this.channelRegistry.channelsNamed(name);
808
+ const shared = mine ? undefined : this.channelRegistry.sharedNameRefusalFor(name);
809
+ if (shared) return { success: false, error: shared, isError: true };
810
+ const channel = named[0];
811
+ if (!channel || !this.channelRegistry.getTuneOutState(channel.serverId, channel.channelId)) {
812
+ return { success: false, error: this.notTunedOut(name, 'the resident'), isError: true };
813
+ }
769
814
  const note = typeof input.text === 'string' && input.text.trim() ? input.text.trim() : undefined;
770
- const result = this.cancel(located.serverId, channelId, 'subconscious', 'subconscious judgment', note);
815
+ const result = this.cancel(channel.serverId, channel.channelId, 'subconscious', 'subconscious judgment', note);
771
816
  return result.ok
772
817
  ? { success: true, data: { cancelled: true } }
773
818
  : { success: false, error: result.error, isError: true };
@@ -817,7 +862,11 @@ export class TuneOutCoordinator {
817
862
  if (!text) {
818
863
  return { success: false, error: 'Nothing was posted: speak_in_channel needs the message in "text".', isError: true };
819
864
  }
820
- return this.channelRegistry.publishForAgent(channelId, text, this.hooks.subconsciousName() ?? 'Subconscious');
865
+ // Inside the calling turn, the id of one of the turn's own channels
866
+ // means that channel, as for every channel tool (ownChannelFor).
867
+ const mine = this.channelRegistry.ownChannelFor(channelId, own, 'publish');
868
+ if (mine && 'error' in mine) return { success: false, error: mine.error, isError: true };
869
+ return this.channelRegistry.publishForAgent(mine ? mine.channel : channelId, text, this.hooks.subconsciousName() ?? 'Subconscious');
821
870
  }
822
871
  default:
823
872
  return { success: false, error: `unknown subconscious tool: ${name}`, isError: true };
@@ -825,32 +874,26 @@ export class TuneOutCoordinator {
825
874
  }
826
875
 
827
876
  /**
828
- * A cancel of a channel that isn't tuned out: which channels are, so the
829
- * next call can name one, and how a tune-out ends without one. `backlogTo`
877
+ * A cancel of a channel that isn't tuned out, by the name the caller gave
878
+ * or knows it by: which channels are, so the next call can name one, and
879
+ * how a tune-out ends without one. `backlogTo`
830
880
  * names the resident as the message's reader knows it: "you" to the
831
881
  * resident, "the resident" to the subconscious. It speaks of connected
832
882
  * channels because listActive reads the registered ones: removing a server
833
883
  * drops its channels and keeps their tune-outs, so a tune-out there is in
834
- * neither this list nor findByChannel's lookup, and the text claims nothing
835
- * about it.
884
+ * neither this list nor the name's lookup (channelsNamed, which reads the
885
+ * registered channels too), and the text claims nothing about it.
836
886
  */
837
- private notTunedOut(channelId: string, backlogTo: 'you' | 'the resident'): string {
838
- const active = this.listActive().map((e) => JSON.stringify(e.channelId));
887
+ private notTunedOut(name: string, backlogTo: 'you' | 'the resident'): string {
888
+ const active = this.listActive().map((e) => JSON.stringify(this.shown(e.serverId, e.channelId)));
839
889
  const opening = active.length === 0
840
890
  ? 'no connected channel is tuned out now.'
841
- : `no connected channel ${JSON.stringify(channelId)} is tuned out. Connected channels tuned out now: `
891
+ : `no connected channel ${JSON.stringify(name)} is tuned out. Connected channels tuned out now: `
842
892
  + `${active.join(', ')}.`;
843
893
  return `Nothing was cancelled: ${opening} A tune-out also ends by itself when its duration runs out, or at the `
844
894
  + `first wake past its wake budget, and its backlog goes to ${backlogTo} then.`;
845
895
  }
846
896
 
847
- private findByChannel(channelId: string): { serverId: string } | null {
848
- for (const entry of this.listActive()) {
849
- if (entry.channelId === channelId) return { serverId: entry.serverId };
850
- }
851
- return null;
852
- }
853
-
854
897
  /** Stop all timers (framework shutdown). */
855
898
  stop(): void {
856
899
  for (const timer of this.cadenceTimers.values()) clearTimeout(timer);
@@ -2,6 +2,7 @@ import type { ContentBlock, YieldingStream } from '@animalabs/membrane';
2
2
  import type { ContextStrategy } from '@animalabs/context-manager';
3
3
  import type { ToolCallId, ToolResult, ToolCall } from './events.js';
4
4
  import type { SilentHeartbeatTick } from '../silent-heartbeat.js';
5
+ import type { WakeLine } from '../gate/types.js';
5
6
 
6
7
  export type SameRoundThinkTextPolicy = 'public' | 'private';
7
8
  export type SameRoundThinkTextPolicySource =
@@ -9,6 +10,24 @@ export type SameRoundThinkTextPolicySource =
9
10
  | 'recipe'
10
11
  | 'compatibility_default';
11
12
 
13
+ /** What a continuation's `[continue]` row says (InferenceRequest.autoContinueRun). */
14
+ export interface AutoContinueRun {
15
+ /** How the agent stops, as the row puts it ("call skip_reply"). */
16
+ stop: string;
17
+ /** The turn before ended in words that were held, not sent, because it
18
+ * had already sent with a send tool. */
19
+ heldAfterSend?: boolean;
20
+ /** Set by the scheduler: the continuation is the turn's only cause. */
21
+ sole?: boolean;
22
+ }
23
+
24
+ /** `AgentConfig.autoContinue` as an object (`true` means `{}`). */
25
+ export interface AutoContinueConfig {
26
+ /** Continuations in a row before the agent waits for the next event:
27
+ * a whole number, 1 to 20. Default 3. */
28
+ maxConsecutive?: number;
29
+ }
30
+
12
31
  /**
13
32
  * Configuration for an agent.
14
33
  */
@@ -62,7 +81,11 @@ export interface AgentConfig {
62
81
  temperature?: number;
63
82
 
64
83
  /** Max input tokens before framework breaks a yielding stream and
65
- * restarts with recompiled (compressed) context. Default: 150000. */
84
+ * restarts with recompiled (compressed) context. Default: 150000. Not
85
+ * checked mid-turn for an agent whose model binds its thinking
86
+ * (`thinkingBinding.binds`) with `physicalWindowTokens` set: a new stream
87
+ * there would take the turn's own earlier reasoning out of the request, and
88
+ * the window projection still guards the provider's hard cap. */
66
89
  maxStreamTokens?: number;
67
90
 
68
91
  /**
@@ -75,7 +98,9 @@ export interface AgentConfig {
75
98
  * request the provider will 400 (issue #92: a legal compile can walk past
76
99
  * the physical window mid-turn). The projection runs once a call on the
77
100
  * stream has reported its prompt. Unset → no projection; only the
78
- * maxStreamTokens restart applies.
101
+ * maxStreamTokens restart applies. A value is converted to a number as a
102
+ * comparison converts it, so a string such as "200000" reads as its number;
103
+ * one whose number isn't finite, such as null, reads as unset.
79
104
  */
80
105
  physicalWindowTokens?: number;
81
106
 
@@ -133,7 +158,9 @@ export interface AgentConfig {
133
158
  * Extended thinking config. When `enabled: true`, the agent runs with
134
159
  * Anthropic's native extended thinking; responses include `thinking` blocks
135
160
  * with cryptographic signatures, and the API enforces `temperature: 1`
136
- * (Membrane handles this). Omit or set `enabled: false` to disable.
161
+ * (Membrane handles this). Omit or set `enabled: false` to disable, except
162
+ * on a model whose thinking is always on (Fable, Mythos), which thinks
163
+ * whatever this says.
137
164
  */
138
165
  thinking?: {
139
166
  enabled: boolean;
@@ -143,10 +170,98 @@ export interface AgentConfig {
143
170
  /**
144
171
  * How thinking content is returned: 'summarized' (readable summary) or
145
172
  * 'omitted' (empty thinking field, signature only). Fable 5 / Opus 4.7+
146
- * default to 'omitted' — set 'summarized' to receive thinking text.
173
+ * default to 'omitted' — set 'summarized' to receive thinking text. A
174
+ * model whose thinking is always on (Fable, Mythos) is sent a thinking
175
+ * config only to carry `thinkingBinding.dropBlock`, so this reaches it
176
+ * only with that set.
147
177
  */
148
178
  display?: 'summarized' | 'omitted';
149
179
  };
180
+ /**
181
+ * How the provider binds this agent's signed thinking to the conversation
182
+ * before it. A host sets it from what it knows of the agent's model and
183
+ * route.
184
+ */
185
+ thinkingBinding?: {
186
+ /**
187
+ * Ask the provider to drop a signed thinking block whose earlier
188
+ * conversation has changed, as a fold changes it, rather than refuse the
189
+ * request. An account that enforces the binding otherwise answers such a
190
+ * request with a 400. One that doesn't decides for itself: the four
191
+ * accounts tried on 2026-10-18 dropped such blocks silently, so there the
192
+ * model sees nothing different and the report is what's new, and one
193
+ * that would show such a block to the model is asked to drop it instead.
194
+ *
195
+ * It's sent as `thinking.block_binding.prefix_mismatch_behavior:
196
+ * "drop_block"` with the `thinking-binding-controls-2026-08-01` beta,
197
+ * which membrane's Anthropic and Bedrock adapters carry (Bedrock's
198
+ * acceptance of the beta hasn't been tried). On any other adapter it does
199
+ * nothing, so a host should refuse it there. A thinking config the host
200
+ * sends itself, through `request.extra` or as `thinking` in
201
+ * `providerParams`, replaces the one membrane builds, control included.
202
+ *
203
+ * On a model whose thinking is always on (Fable, Mythos), membrane
204
+ * carries it in an adaptive thinking config whatever `thinking.enabled`
205
+ * says, with `thinking.display` when that's set, so such an agent's
206
+ * `display` takes effect only with this setting. On any other model, it
207
+ * goes only in the thinking config membrane sends while thinking is on.
208
+ * Membrane tells the first kind by its id, which names `fable` or
209
+ * `mythos`. Opus 5.5, Haiku 5.5, Sonnet 5.5, Sonnet 5, Fable 5 and Fable
210
+ * 5.1 accepted the config on 2026-10-18. Mythos hasn't been tried, and
211
+ * membrane notes that the family has refused thinking configs with a 400
212
+ * before, so for a Mythos model the setting may fail every request.
213
+ *
214
+ * On a framework turn, each call's report of the blocks the provider
215
+ * dropped goes to its `inference:usage` trace, and neither that call nor
216
+ * its stream's later calls feed the context strategy's calibration, since
217
+ * the provider doesn't bill a block it dropped and the strategy still
218
+ * counts it. After a fold, each block after the stretch it rewrote is
219
+ * bound to a conversation the request no longer holds. Where the context
220
+ * strategy still sends such a block, the provider drops it on every call
221
+ * until a later fold takes it out of the context, and while such blocks
222
+ * are sent, no stream gives a sample and the calibration keeps the value
223
+ * it had. A strategy that leaves that thinking out at the fold sends none
224
+ * to drop. The direct API carries the control too, but reads no report: it
225
+ * traces no usage and feeds no calibration.
226
+ *
227
+ * Default false: nothing is sent, and the account's default applies.
228
+ */
229
+ dropBlock?: boolean;
230
+ /**
231
+ * Whether this agent's model binds its signed thinking to the
232
+ * conversation before it, as the host knows from its own model records.
233
+ * A context manager that keeps a record of what each branch sent can
234
+ * then leave out thinking whose earlier conversation changed before the
235
+ * provider sees it. Set:
236
+ * - each compile the agent sends tells the context manager so
237
+ * (`thinkingBinding`: `binds`, `dropBlock` as set here, and the agent's
238
+ * name as the `participant` whose replies are its own), with digests
239
+ * of what the request carries ahead of its messages (`identity`: the
240
+ * system text with its system-position injections, the tools in the
241
+ * order sent, the model, and the thinking config, `maxTokens`,
242
+ * `providerParams` and `prefillUserMessage`);
243
+ * - each framework stream tells the context manager which stored rows
244
+ * its later rounds and its last reply carried as stored
245
+ * (`noteRequestSent`), naming the compile it was built from, so their
246
+ * thinking is kept at the next compile. The direct API
247
+ * (`runInference`) notes nothing, so each of its replies' thinking is
248
+ * left out at the compile after it's stored;
249
+ * - with `physicalWindowTokens` set too, `maxStreamTokens` no longer
250
+ * starts a new stream mid-turn, since the new stream's compile can take
251
+ * the turn's earlier reasoning out of the request;
252
+ * - with `dropBlock` set too, where a compile reports where the branch's
253
+ * record began (`thinkingRecordedAfter`), a block the provider drops
254
+ * that the record doesn't account for raises the
255
+ * `thinking-binding-loss` ops alert, once per incident and again if a
256
+ * call drops more. The provider reports drops only on a request that
257
+ * carries the drop control; without it, such a block is refused.
258
+ * The record and the notes
259
+ * vouch for requests as agent-framework hands them to membrane, so a
260
+ * membrane `beforeRequest` hook that changes a request must change every
261
+ * request alike. Default false: compiles and streams are as they were.
262
+ */
263
+ binds?: boolean;
264
+ };
150
265
  /**
151
266
  * How to handle a model content-policy refusal (`stop_reason: refusal`).
152
267
  * When `autoRewind` is on, a refused turn triggers an automatic rewind: the
@@ -219,6 +334,34 @@ export interface AgentConfig {
219
334
  */
220
335
  toolWrapperProseGuard?: boolean;
221
336
 
337
+ /**
338
+ * Continue on the agent's own turn ending (off when left out): a turn the
339
+ * model ends itself (stop reason `end_turn` or `stop_sequence`), whether
340
+ * its last response is prose or empty, is followed by another turn,
341
+ * instead of a pause until the next event. A turn ended through
342
+ * `skip_reply` (or another tool that ends the turn, such as `sleep`), or
343
+ * whose last prose envelope is `>>skip_reply` (explicit) or `>>>skip_reply`
344
+ * (hybrid), pauses as usual. So does one whose closing prose bounced (the
345
+ * bounce and its cap answer it), and an agent offered no way to stop
346
+ * (locus or disabled mode without an offered `skip_reply`).
347
+ *
348
+ * As the next turn starts, a short `[continue]` system row naming the agent
349
+ * joins the window: another turn starts, how to stop, and that unmarked
350
+ * words are sent as usual; on the last continuation of a run, that the
351
+ * agent waits for the next event after it. It's written only when the
352
+ * continuation is that turn's whole cause: a message batched with it makes
353
+ * it that message's turn, with no row, in the message's channel. Otherwise
354
+ * the next turn keeps the turn's locus (locus and hybrid modes). At most
355
+ * `maxConsecutive` (default 3) continuations run in a row: the count
356
+ * starts again when a turn starts with a cause of its own (a message, a
357
+ * timer, a heartbeat that isn't a silent tick) or a tool ends one. Silent
358
+ * heartbeat ticks, refusals, `max_tokens` cut-offs, the stall guard's
359
+ * ends, ephemeral runs, conversation forks and a quiesce never continue. A
360
+ * ` !` continuation the turn asked for runs instead, and counts toward
361
+ * nothing. See docs/auto-continue.md.
362
+ */
363
+ autoContinue?: boolean | AutoContinueConfig;
364
+
222
365
  /**
223
366
  * Clock rows (off when left out): when a boundary of this interval on the
224
367
  * agent's local wall clock (counted from local midnight, in the
@@ -399,9 +542,13 @@ export interface InferenceRequest {
399
542
  * all of them without materializing content, the turn has no cause left
400
543
  * and does not run. */
401
544
  coalescingBatchSubjects?: string[];
402
- /** Host-owned: this wake's sole cause is reading this deferred write, a
403
- * coalesced render placed in its agent's queue (wakeToRead). Once the
404
- * agent has read the write, or it was removed, the wake starts no turn. */
545
+ /** Host-owned: this wake's sole cause is this write, deferred for its
546
+ * agent while a turn was alive: a coalesced render placed in its queue
547
+ * (wakeToRead), or a message, notice or event whose wake was queued with
548
+ * it. Once a request that carried the write was answered and, for a wake
549
+ * that carries a channel, the agent has since published into that
550
+ * channel having read it, or once the write is in neither the agent's
551
+ * queue nor its store's current branch, the wake starts no turn. */
405
552
  readsDeferredWrite?: string;
406
553
  agentName: string;
407
554
  reason: string;
@@ -418,6 +565,25 @@ export interface InferenceRequest {
418
565
  * ephemeral run's (a subagent's), which takes no global fallback.
419
566
  */
420
567
  channelId?: string;
568
+ /**
569
+ * The server of `channelId`'s channel. MCPL requires a channel id to be
570
+ * unique only within one server's connection (§14.2), so where two
571
+ * servers register the same id only the pair names one channel. The
572
+ * framework's own wakes set it beside `channelId`. A host's request that
573
+ * gives `channelId` alone names the one channel that id, or its
574
+ * server-qualified name (`serverId:channelId`), means; one that two
575
+ * servers' channels answer to names none, and the turn's speech has no
576
+ * channel of its own.
577
+ */
578
+ channelServerId?: string;
579
+ /**
580
+ * Framework-set: this wake carries an autoContinue continuation
581
+ * (AgentConfig.autoContinue), with what its `[continue]` row says. The
582
+ * scheduler keeps the mark on the turn's trigger only when the
583
+ * continuation is that turn's whole cause, and the turn writes the row as
584
+ * it starts. Hosts don't set it.
585
+ */
586
+ autoContinueRun?: AutoContinueRun;
421
587
  /**
422
588
  * True when the triggering message explicitly addressed the agent (mention,
423
589
  * reply-to-bot, DM — `chat:addressed` in MCPL RFC-001 terms). When a wake
@@ -426,6 +592,16 @@ export interface InferenceRequest {
426
592
  * not capture the agent's voice just by being newest.
427
593
  */
428
594
  addressed?: boolean;
595
+ /**
596
+ * Host-owned: true when the triggering message's server marked it ambient
597
+ * (`chat:ambient`). A coalesced push's wake carries it only to read a
598
+ * render, and only when every notice the render stands for was. Such a
599
+ * message asked nothing of the agent, so a wake for it that carries
600
+ * `readsDeferredWrite` is dropped once a request that carried the message
601
+ * was answered, with no publish asked of the agent. A message with neither
602
+ * tag is in doubt, and is judged as an addressed one.
603
+ */
604
+ ambient?: true;
429
605
  /**
430
606
  * Who spoke the message that triggered this inference, as an adapter-
431
607
  * namespaced opaque id (`<serverId>:user:<authorId>`) — never a display
@@ -446,6 +622,12 @@ export interface InferenceRequest {
446
622
  wakeChannelId?: string;
447
623
  /** Timestamp of the event the wake provenance was taken from (ms). */
448
624
  wakeAt?: number;
625
+ /** Batched-wake lines the gate handed to this wake rather than wrote
626
+ * (WakeLine), decided by a fresh start of its turn as WakeLine says: each
627
+ * written once into the window it names, or left for a later fresh start
628
+ * of the same wake. A retry, or a start that resumes a logical turn,
629
+ * decides none. */
630
+ wakeLines?: WakeLine[];
449
631
  /** Suppress every automatic plain-prose delivery for this logical turn.
450
632
  * Explicit tool calls remain available. Used by authenticated silent wakes. */
451
633
  suppressProse?: boolean;
@@ -194,6 +194,18 @@ export interface ToolResult {
194
194
  * it reads the result's error text, if any.
195
195
  */
196
196
  data?: unknown;
197
+ /**
198
+ * Whether `data` is a protocol result's content: an array in which every
199
+ * element is a content block, of a type this host can show or not. The
200
+ * framework sets it on an MCPL tool's result, and a module that returns a
201
+ * content array can set it too. When it's set, each element reaches the
202
+ * model as itself, as a reference's stub, or as a marker saying what it was
203
+ * and why it isn't shown, never as JSON. When it isn't, an array counts as
204
+ * content only when every element is a block of one of MCP's types (text,
205
+ * image, audio, resource, resource_link), and any other array is data,
206
+ * written as JSON.
207
+ */
208
+ dataIsContent?: boolean;
197
209
  /** Error message (if failure) */
198
210
  error?: string;
199
211
  /** Whether this was an error (for LLM) */
@@ -299,7 +311,10 @@ export interface ToolCall {
299
311
  * leaves room for them; and a result nested past 2,000 levels, or whose parse
300
312
  * needs more of the script's memory than is free, doesn't reach the script
301
313
  * whatever its length. A result that doesn't reach it is answered
302
- * ResultTooLarge, the call having taken effect. Nothing is cut or saved to
314
+ * ResultTooLarge, the call having taken effect. One the host can't convert
315
+ * to JSON at all, such as one nested deeper than its runtime's
316
+ * JSON.stringify can take, is answered instead as a failure naming the
317
+ * tool and saying the call itself completed. Nothing is cut or saved to
303
318
  * a file. A tool that pages or budgets its answer can stay under it. The
304
319
  * framework sets it on a model's, a script's and the operator's puppet's
305
320
  * call to a module, a utility run through `utils` included. A call
@@ -308,7 +323,9 @@ export interface ToolCall {
308
323
  * read_image tool passes a model's call to the workspace without it; and a
309
324
  * utility run through `utils` carries what its `utils` call carried.
310
325
  * Without it, a module holds its answer to a bound of its own, if any: the
311
- * workspace's is the house default, 24,000.
326
+ * workspace's `edit` and `edit_many` answers hold to the house default,
327
+ * 24,000, and a `read` holds its page to its line limit alone, with every
328
+ * line whole, since such a caller takes the data itself.
312
329
  */
313
330
  resultMaxChars?: number;
314
331
  }
@@ -16,6 +16,13 @@ export type { TraceEvent, TraceEventListener } from './trace.js';
16
16
  export interface ModuleProcessResponse {
17
17
  moduleName: string;
18
18
  response: EventResponse;
19
+ /**
20
+ * Set when applying the response threw: the operation it threw at, such
21
+ * as `the edit of <id>`, and the error's message. What the response asked
22
+ * for before that was applied, that operation may have taken effect in
23
+ * part or whole, and the rest of it wasn't (EventResponse).
24
+ */
25
+ threw?: { at: string; error: string };
19
26
  }
20
27
 
21
28
  /**
@@ -241,8 +248,9 @@ export interface FrameworkConfig {
241
248
  * workspace file under `tool-results/` (deterministic `<date>-<callId>.txt`
242
249
  * name, overwritten on collision, retained until the workspace owner
243
250
  * deletes it — never auto-GC'd) and replaced inline by a bounded preview
244
- * plus the file reference; with no writable mount without an include, the
245
- * fallback is explicit plain truncation. Default 24000 (house-safe; issue #89). Must be
251
+ * plus the file reference; with no writable mount without an include that
252
+ * keeps its files in the store (not configured direct), the fallback is
253
+ * explicit plain truncation. Default 24000 (house-safe; issue #89). Must be
246
254
  * >= 1000. A resident's own agent_settings value
247
255
  * `tool_result_inline_max_chars` (durable, persisted in framework state)
248
256
  * takes precedence over this for that agent; the EFFECTIVE cap for every
@@ -276,7 +284,17 @@ export interface FrameworkConfig {
276
284
  */
277
285
  hostToolClasses?: Record<string, string[]>;
278
286
 
279
- /** Inference routing policy for server-initiated inference (optional). */
287
+ /**
288
+ * Inference routing policy for server-initiated inference (optional): the
289
+ * model an MCPL server's inference/request runs on. Without one, each such
290
+ * request is refused with -32603 and a message naming this setting; the
291
+ * framework doesn't choose a model for a server. A server's effective
292
+ * grant can still list `inferenceRequest` then, since the grant comes from
293
+ * what the server advertises and `enabledCapabilities`, not from this
294
+ * policy. A policy that names no model for a request's feature set (no
295
+ * override, byFeature or wildcards entry that names one, and an empty
296
+ * `default`) refuses that request the same way, naming the feature set.
297
+ */
280
298
  inferenceRouting?: InferenceRoutingPolicy;
281
299
 
282
300
  /** EventGate config. If omitted, all events trigger inference (unchanged default). */