@animalabs/agent-framework 0.31.0 → 0.33.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 (298) hide show
  1. package/README.md +204 -14
  2. package/dist/src/admission/admit.d.ts +18 -0
  3. package/dist/src/admission/admit.d.ts.map +1 -1
  4. package/dist/src/admission/admit.js +16 -3
  5. package/dist/src/admission/admit.js.map +1 -1
  6. package/dist/src/agent.d.ts +185 -78
  7. package/dist/src/agent.d.ts.map +1 -1
  8. package/dist/src/agent.js +279 -102
  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 +50 -12
  12. package/dist/src/api/mcp-server-core.js.map +1 -1
  13. package/dist/src/api/server.d.ts.map +1 -1
  14. package/dist/src/api/server.js +4 -20
  15. package/dist/src/api/server.js.map +1 -1
  16. package/dist/src/code-execution/isolate/scripts.d.ts +12 -8
  17. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  18. package/dist/src/code-execution/isolate/scripts.js +24 -0
  19. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  20. package/dist/src/config-checks.d.ts +7 -0
  21. package/dist/src/config-checks.d.ts.map +1 -1
  22. package/dist/src/config-checks.js +14 -0
  23. package/dist/src/config-checks.js.map +1 -1
  24. package/dist/src/context-manager-errors.d.ts +25 -0
  25. package/dist/src/context-manager-errors.d.ts.map +1 -0
  26. package/dist/src/context-manager-errors.js +31 -0
  27. package/dist/src/context-manager-errors.js.map +1 -0
  28. package/dist/src/context-options.d.ts +67 -0
  29. package/dist/src/context-options.d.ts.map +1 -0
  30. package/dist/src/context-options.js +85 -0
  31. package/dist/src/context-options.js.map +1 -0
  32. package/dist/src/deferred-import.d.ts +93 -0
  33. package/dist/src/deferred-import.d.ts.map +1 -0
  34. package/dist/src/deferred-import.js +177 -0
  35. package/dist/src/deferred-import.js.map +1 -0
  36. package/dist/src/dendrite/audience.d.ts +223 -0
  37. package/dist/src/dendrite/audience.d.ts.map +1 -0
  38. package/dist/src/dendrite/audience.js +202 -0
  39. package/dist/src/dendrite/audience.js.map +1 -0
  40. package/dist/src/dendrite/index.d.ts +5 -3
  41. package/dist/src/dendrite/index.d.ts.map +1 -1
  42. package/dist/src/dendrite/index.js +2 -1
  43. package/dist/src/dendrite/index.js.map +1 -1
  44. package/dist/src/dendrite/presets.d.ts +10 -5
  45. package/dist/src/dendrite/presets.d.ts.map +1 -1
  46. package/dist/src/dendrite/presets.js +17 -5
  47. package/dist/src/dendrite/presets.js.map +1 -1
  48. package/dist/src/dendrite/registry.d.ts +62 -7
  49. package/dist/src/dendrite/registry.d.ts.map +1 -1
  50. package/dist/src/dendrite/registry.js +179 -10
  51. package/dist/src/dendrite/registry.js.map +1 -1
  52. package/dist/src/dendrite/types.d.ts +91 -8
  53. package/dist/src/dendrite/types.d.ts.map +1 -1
  54. package/dist/src/foreign-rounds.d.ts +69 -0
  55. package/dist/src/foreign-rounds.d.ts.map +1 -0
  56. package/dist/src/foreign-rounds.js +152 -0
  57. package/dist/src/foreign-rounds.js.map +1 -0
  58. package/dist/src/framework.d.ts +1977 -820
  59. package/dist/src/framework.d.ts.map +1 -1
  60. package/dist/src/framework.js +7571 -3457
  61. package/dist/src/framework.js.map +1 -1
  62. package/dist/src/gate/event-gate.d.ts +85 -15
  63. package/dist/src/gate/event-gate.d.ts.map +1 -1
  64. package/dist/src/gate/event-gate.js +421 -47
  65. package/dist/src/gate/event-gate.js.map +1 -1
  66. package/dist/src/gate/index.d.ts +1 -1
  67. package/dist/src/gate/index.d.ts.map +1 -1
  68. package/dist/src/gate/index.js.map +1 -1
  69. package/dist/src/gate/rule-scope.d.ts +30 -0
  70. package/dist/src/gate/rule-scope.d.ts.map +1 -0
  71. package/dist/src/gate/rule-scope.js +41 -0
  72. package/dist/src/gate/rule-scope.js.map +1 -0
  73. package/dist/src/gate/suppress-script-worker.d.ts +2 -0
  74. package/dist/src/gate/suppress-script-worker.d.ts.map +1 -0
  75. package/dist/src/gate/suppress-script-worker.js +103 -0
  76. package/dist/src/gate/suppress-script-worker.js.map +1 -0
  77. package/dist/src/gate/suppress-scripts.d.ts +105 -0
  78. package/dist/src/gate/suppress-scripts.d.ts.map +1 -0
  79. package/dist/src/gate/suppress-scripts.js +403 -0
  80. package/dist/src/gate/suppress-scripts.js.map +1 -0
  81. package/dist/src/gate/suppression-review.d.ts +143 -0
  82. package/dist/src/gate/suppression-review.d.ts.map +1 -0
  83. package/dist/src/gate/suppression-review.js +463 -0
  84. package/dist/src/gate/suppression-review.js.map +1 -0
  85. package/dist/src/gate/suppression-stats.d.ts +125 -0
  86. package/dist/src/gate/suppression-stats.d.ts.map +1 -0
  87. package/dist/src/gate/suppression-stats.js +214 -0
  88. package/dist/src/gate/suppression-stats.js.map +1 -0
  89. package/dist/src/gate/types.d.ts +135 -7
  90. package/dist/src/gate/types.d.ts.map +1 -1
  91. package/dist/src/gate/wake-onboarding.d.ts +119 -0
  92. package/dist/src/gate/wake-onboarding.d.ts.map +1 -0
  93. package/dist/src/gate/wake-onboarding.js +277 -0
  94. package/dist/src/gate/wake-onboarding.js.map +1 -0
  95. package/dist/src/held-writes.d.ts +31 -23
  96. package/dist/src/held-writes.d.ts.map +1 -1
  97. package/dist/src/held-writes.js +44 -59
  98. package/dist/src/held-writes.js.map +1 -1
  99. package/dist/src/index.d.ts +5 -2
  100. package/dist/src/index.d.ts.map +1 -1
  101. package/dist/src/index.js +3 -0
  102. package/dist/src/index.js.map +1 -1
  103. package/dist/src/jsonrpc-envelope.d.ts +69 -0
  104. package/dist/src/jsonrpc-envelope.d.ts.map +1 -0
  105. package/dist/src/jsonrpc-envelope.js +66 -0
  106. package/dist/src/jsonrpc-envelope.js.map +1 -0
  107. package/dist/src/mcpl/channel-registry.d.ts +131 -15
  108. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  109. package/dist/src/mcpl/channel-registry.js +290 -46
  110. package/dist/src/mcpl/channel-registry.js.map +1 -1
  111. package/dist/src/mcpl/checkpoint-manager.d.ts +2 -4
  112. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  113. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  114. package/dist/src/mcpl/feature-set-manager.d.ts +2 -4
  115. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  116. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  117. package/dist/src/mcpl/inbound-source.d.ts +2 -1
  118. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  119. package/dist/src/mcpl/inbound-source.js +2 -1
  120. package/dist/src/mcpl/inbound-source.js.map +1 -1
  121. package/dist/src/mcpl/inference-router.d.ts +6 -6
  122. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  123. package/dist/src/mcpl/inference-router.js.map +1 -1
  124. package/dist/src/mcpl/push-coalescer.d.ts +132 -22
  125. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  126. package/dist/src/mcpl/push-coalescer.js +105 -15
  127. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  128. package/dist/src/mcpl/push-handler.d.ts +9 -7
  129. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  130. package/dist/src/mcpl/push-handler.js.map +1 -1
  131. package/dist/src/mcpl/server-connection.d.ts +13 -10
  132. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  133. package/dist/src/mcpl/server-connection.js +134 -56
  134. package/dist/src/mcpl/server-connection.js.map +1 -1
  135. package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
  136. package/dist/src/mcpl/tool-classes.js +3 -0
  137. package/dist/src/mcpl/tool-classes.js.map +1 -1
  138. package/dist/src/mcpl/types.d.ts +8 -2
  139. package/dist/src/mcpl/types.d.ts.map +1 -1
  140. package/dist/src/mcpl/types.js.map +1 -1
  141. package/dist/src/module-registry.d.ts +24 -1
  142. package/dist/src/module-registry.d.ts.map +1 -1
  143. package/dist/src/module-registry.js +68 -18
  144. package/dist/src/module-registry.js.map +1 -1
  145. package/dist/src/modules/discord/index.d.ts +45 -8
  146. package/dist/src/modules/discord/index.d.ts.map +1 -1
  147. package/dist/src/modules/discord/index.js +237 -86
  148. package/dist/src/modules/discord/index.js.map +1 -1
  149. package/dist/src/modules/history/index.d.ts +59 -0
  150. package/dist/src/modules/history/index.d.ts.map +1 -1
  151. package/dist/src/modules/history/index.js +432 -67
  152. package/dist/src/modules/history/index.js.map +1 -1
  153. package/dist/src/modules/history/semantic.d.ts +6 -5
  154. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  155. package/dist/src/modules/history/semantic.js +15 -22
  156. package/dist/src/modules/history/semantic.js.map +1 -1
  157. package/dist/src/modules/workspace/disk-agreement.d.ts +1 -1
  158. package/dist/src/modules/workspace/disk-agreement.js +1 -1
  159. package/dist/src/modules/workspace/index.d.ts +28 -7
  160. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  161. package/dist/src/modules/workspace/index.js +76 -22
  162. package/dist/src/modules/workspace/index.js.map +1 -1
  163. package/dist/src/notice-tags.d.ts +10 -1
  164. package/dist/src/notice-tags.d.ts.map +1 -1
  165. package/dist/src/notice-tags.js +10 -1
  166. package/dist/src/notice-tags.js.map +1 -1
  167. package/dist/src/operator-log.d.ts +36 -3
  168. package/dist/src/operator-log.d.ts.map +1 -1
  169. package/dist/src/operator-log.js +2 -2
  170. package/dist/src/operator-log.js.map +1 -1
  171. package/dist/src/presentation.d.ts +746 -0
  172. package/dist/src/presentation.d.ts.map +1 -0
  173. package/dist/src/presentation.js +1435 -0
  174. package/dist/src/presentation.js.map +1 -0
  175. package/dist/src/prose-drafts.d.ts +15 -11
  176. package/dist/src/prose-drafts.d.ts.map +1 -1
  177. package/dist/src/prose-drafts.js +16 -12
  178. package/dist/src/prose-drafts.js.map +1 -1
  179. package/dist/src/read-marks.d.ts +192 -0
  180. package/dist/src/read-marks.d.ts.map +1 -0
  181. package/dist/src/read-marks.js +242 -0
  182. package/dist/src/read-marks.js.map +1 -0
  183. package/dist/src/recovery/offline-branch.d.ts +91 -6
  184. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  185. package/dist/src/recovery/offline-branch.js +273 -29
  186. package/dist/src/recovery/offline-branch.js.map +1 -1
  187. package/dist/src/recovery/recover-cli.js +22 -3
  188. package/dist/src/recovery/recover-cli.js.map +1 -1
  189. package/dist/src/send-tools.d.ts +5 -9
  190. package/dist/src/send-tools.d.ts.map +1 -1
  191. package/dist/src/send-tools.js +40 -7
  192. package/dist/src/send-tools.js.map +1 -1
  193. package/dist/src/stored-injections.d.ts +2 -2
  194. package/dist/src/stored-injections.js +2 -2
  195. package/dist/src/stored-removal.d.ts +17 -4
  196. package/dist/src/stored-removal.d.ts.map +1 -1
  197. package/dist/src/stored-removal.js +29 -5
  198. package/dist/src/stored-removal.js.map +1 -1
  199. package/dist/src/tool-result-guard.d.ts +12 -1
  200. package/dist/src/tool-result-guard.d.ts.map +1 -1
  201. package/dist/src/tool-result-guard.js +42 -8
  202. package/dist/src/tool-result-guard.js.map +1 -1
  203. package/dist/src/tune-out/coordinator.d.ts +1159 -79
  204. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  205. package/dist/src/tune-out/coordinator.js +3709 -363
  206. package/dist/src/tune-out/coordinator.js.map +1 -1
  207. package/dist/src/tune-out/tools.d.ts +114 -17
  208. package/dist/src/tune-out/tools.d.ts.map +1 -1
  209. package/dist/src/tune-out/tools.js +87 -27
  210. package/dist/src/tune-out/tools.js.map +1 -1
  211. package/dist/src/tune-out/window-anchor.d.ts +143 -0
  212. package/dist/src/tune-out/window-anchor.d.ts.map +1 -0
  213. package/dist/src/tune-out/window-anchor.js +202 -0
  214. package/dist/src/tune-out/window-anchor.js.map +1 -0
  215. package/dist/src/types/agent.d.ts +86 -31
  216. package/dist/src/types/agent.d.ts.map +1 -1
  217. package/dist/src/types/events.d.ts +2 -0
  218. package/dist/src/types/events.d.ts.map +1 -1
  219. package/dist/src/types/framework.d.ts +43 -21
  220. package/dist/src/types/framework.d.ts.map +1 -1
  221. package/dist/src/types/module.d.ts +180 -69
  222. package/dist/src/types/module.d.ts.map +1 -1
  223. package/dist/src/types/trace.d.ts +510 -36
  224. package/dist/src/types/trace.d.ts.map +1 -1
  225. package/package.json +3 -3
  226. package/src/admission/admit.ts +32 -3
  227. package/src/agent.ts +323 -120
  228. package/src/api/mcp-server-core.ts +54 -17
  229. package/src/api/server.ts +4 -23
  230. package/src/code-execution/isolate/scripts.ts +28 -3
  231. package/src/config-checks.ts +14 -0
  232. package/src/context-manager-errors.ts +37 -0
  233. package/src/context-options.ts +96 -0
  234. package/src/deferred-import.ts +239 -0
  235. package/src/dendrite/audience.ts +315 -0
  236. package/src/dendrite/index.ts +14 -2
  237. package/src/dendrite/presets.ts +18 -6
  238. package/src/dendrite/registry.ts +209 -12
  239. package/src/dendrite/types.ts +92 -8
  240. package/src/foreign-rounds.ts +199 -0
  241. package/src/framework.ts +7814 -3585
  242. package/src/gate/event-gate.ts +428 -65
  243. package/src/gate/index.ts +3 -0
  244. package/src/gate/rule-scope.ts +45 -0
  245. package/src/gate/suppress-script-worker.ts +89 -0
  246. package/src/gate/suppress-scripts.ts +414 -0
  247. package/src/gate/suppression-review.ts +517 -0
  248. package/src/gate/suppression-stats.ts +314 -0
  249. package/src/gate/types.ts +141 -7
  250. package/src/gate/wake-onboarding.ts +309 -0
  251. package/src/held-writes.ts +49 -60
  252. package/src/index.ts +9 -2
  253. package/src/jsonrpc-envelope.ts +81 -0
  254. package/src/mcpl/channel-registry.ts +353 -85
  255. package/src/mcpl/checkpoint-manager.ts +3 -2
  256. package/src/mcpl/feature-set-manager.ts +2 -1
  257. package/src/mcpl/inbound-source.ts +2 -1
  258. package/src/mcpl/inference-router.ts +7 -4
  259. package/src/mcpl/push-coalescer.ts +193 -34
  260. package/src/mcpl/push-handler.ts +10 -5
  261. package/src/mcpl/server-connection.ts +141 -60
  262. package/src/mcpl/tool-classes.ts +3 -0
  263. package/src/mcpl/types.ts +8 -2
  264. package/src/module-registry.ts +73 -17
  265. package/src/modules/discord/index.ts +229 -81
  266. package/src/modules/history/index.ts +439 -70
  267. package/src/modules/history/semantic.ts +15 -21
  268. package/src/modules/workspace/disk-agreement.ts +1 -1
  269. package/src/modules/workspace/index.ts +85 -23
  270. package/src/notice-tags.ts +10 -1
  271. package/src/operator-log.ts +36 -3
  272. package/src/presentation.ts +1676 -0
  273. package/src/prose-drafts.ts +16 -12
  274. package/src/read-marks.ts +328 -0
  275. package/src/recovery/offline-branch.ts +346 -32
  276. package/src/recovery/recover-cli.ts +21 -3
  277. package/src/send-tools.ts +40 -7
  278. package/src/stored-injections.ts +2 -2
  279. package/src/stored-removal.ts +31 -7
  280. package/src/tool-result-guard.ts +41 -7
  281. package/src/tune-out/coordinator.ts +4089 -457
  282. package/src/tune-out/tools.ts +160 -43
  283. package/src/tune-out/window-anchor.ts +277 -0
  284. package/src/types/agent.ts +90 -31
  285. package/src/types/events.ts +2 -0
  286. package/src/types/framework.ts +43 -21
  287. package/src/types/module.ts +181 -68
  288. package/src/types/trace.ts +542 -40
  289. package/dist/src/reasoning-loss-notice.d.ts +0 -23
  290. package/dist/src/reasoning-loss-notice.d.ts.map +0 -1
  291. package/dist/src/reasoning-loss-notice.js +0 -52
  292. package/dist/src/reasoning-loss-notice.js.map +0 -1
  293. package/dist/src/reasoning-loss.d.ts +0 -24
  294. package/dist/src/reasoning-loss.d.ts.map +0 -1
  295. package/dist/src/reasoning-loss.js +0 -102
  296. package/dist/src/reasoning-loss.js.map +0 -1
  297. package/src/reasoning-loss-notice.ts +0 -64
  298. package/src/reasoning-loss.ts +0 -120
@@ -0,0 +1,314 @@
1
+ /**
2
+ * What the agent's suppress rules have kept out of its context
3
+ * (`suppression_stats`), read from the stored rows themselves: each carries
4
+ * the record a suppress rule stamped on it as it arrived
5
+ * (`metadata.suppressed = { rule, eventType }`, the framework's
6
+ * suppressedRow). The rows are the record, so the counts cover every start
7
+ * of the host and follow the branch the store is on, and nothing beside
8
+ * them is kept.
9
+ */
10
+
11
+ /** A stored row as these stats read it: the parts of context-manager's StoredMessage they use. */
12
+ export interface SuppressionStatsRow {
13
+ id: string;
14
+ timestamp: Date | number | string;
15
+ content: ReadonlyArray<{ type: string; text?: unknown }>;
16
+ metadata?: Record<string, unknown>;
17
+ }
18
+
19
+ /** A rule in gate.json, as the stats name it. */
20
+ export interface SuppressRuleInForce {
21
+ name: string;
22
+ /** Events it suppressed since this process started (the gate's matchCount for it). */
23
+ sinceStart: number;
24
+ }
25
+
26
+ export interface SuppressionStatsOptions {
27
+ /** Only this rule's rows. */
28
+ rule?: string;
29
+ /** How many of each rule's newest rows to show, with a preview. Default 3, at most 20. */
30
+ recent?: number;
31
+ /** Only rows stored at or after this time (ms). */
32
+ sinceMs?: number;
33
+ }
34
+
35
+ /** One suppressed row, as the stats show it. */
36
+ export interface SuppressedEventShown {
37
+ id: string;
38
+ at: string;
39
+ eventType: string;
40
+ source?: string;
41
+ channel?: string;
42
+ author?: string;
43
+ preview: string;
44
+ }
45
+
46
+ /** One rule's stats. */
47
+ export interface RuleSuppressionStats {
48
+ rule: string;
49
+ /** Whether gate.json has a suppress rule of that name now. */
50
+ inForce: boolean;
51
+ /** Present for a rule in force: what it suppressed since this process started. */
52
+ sinceStart?: number;
53
+ /** Stored rows it suppressed (within `since`, when given). */
54
+ suppressed: number;
55
+ /** Their text, in characters. */
56
+ characters: number;
57
+ first?: string;
58
+ last?: string;
59
+ /** Rows by the server (or module source) they came from, the most first; past ten, the rest as "(others)". */
60
+ bySource: Record<string, number>;
61
+ /** Rows by the event type they arrived as. */
62
+ byEventType: Record<string, number>;
63
+ /** The newest rows, newest first. */
64
+ recent: SuppressedEventShown[];
65
+ }
66
+
67
+ export interface SuppressionStats {
68
+ rules: RuleSuppressionStats[];
69
+ /** Every stored suppressed row counted (within `since`, when given). */
70
+ total: number;
71
+ totalCharacters: number;
72
+ }
73
+
74
+ /** Longest preview shown, in characters. */
75
+ export const SUPPRESSION_PREVIEW_CHARS = 160;
76
+ /** Most recent rows shown per rule. */
77
+ export const MAX_RECENT = 20;
78
+ const DEFAULT_RECENT = 3;
79
+ const MAX_KEYS = 10;
80
+
81
+ /** The record a row carries when a suppress rule took it, or undefined. */
82
+ export function suppressionRecordOf(metadata: Record<string, unknown> | undefined): { rule: string; eventType?: string; source?: string } | undefined {
83
+ const record = metadata?.suppressed;
84
+ if (!record || typeof record !== 'object') return undefined;
85
+ const { rule, eventType, source } = record as { rule?: unknown; eventType?: unknown; source?: unknown };
86
+ if (typeof rule !== 'string') return undefined;
87
+ return {
88
+ rule,
89
+ ...(typeof eventType === 'string' && eventType !== '' ? { eventType } : {}),
90
+ ...(typeof source === 'string' && source !== '' ? { source } : {}),
91
+ };
92
+ }
93
+
94
+ /** Where a suppressed row came from: the source its record names (a module's, which its metadata may not), else its server. */
95
+ function sourceOf(row: SuppressionStatsRow, record: { source?: string } | undefined): string | undefined {
96
+ if (record?.source) return record.source;
97
+ const serverId = row.metadata?.serverId;
98
+ return typeof serverId === 'string' && serverId !== '' ? serverId : undefined;
99
+ }
100
+
101
+ /** The author a row names: a channel message's `author.name`, or a push event's `authorName`. */
102
+ function authorOf(md: Record<string, unknown>): string | undefined {
103
+ const name = (md.author as { name?: unknown } | undefined)?.name ?? md.authorName;
104
+ return typeof name === 'string' && name !== '' ? name : undefined;
105
+ }
106
+
107
+ const textOf = (row: SuppressionStatsRow): string =>
108
+ row.content.filter((b) => b.type === 'text' && typeof b.text === 'string').map((b) => b.text as string).join('\n');
109
+
110
+ const msOf = (timestamp: SuppressionStatsRow['timestamp']): number =>
111
+ timestamp instanceof Date ? timestamp.getTime() : typeof timestamp === 'number' ? timestamp : Date.parse(timestamp);
112
+
113
+ const isoOf = (ms: number): string => (Number.isFinite(ms) ? new Date(ms).toISOString() : 'unknown');
114
+
115
+ /** A text on one line, cut at SUPPRESSION_PREVIEW_CHARS characters (code points), with an ellipsis where cut. */
116
+ function previewOf(text: string): string {
117
+ const line = text.replace(/\s+/g, ' ').trim();
118
+ const chars = [...line];
119
+ return chars.length <= SUPPRESSION_PREVIEW_CHARS ? line : `${chars.slice(0, SUPPRESSION_PREVIEW_CHARS - 1).join('')}…`;
120
+ }
121
+
122
+ /** Counts, the largest first; past MAX_KEYS, the rest summed as "(others)". Built from entries, so any name, "__proto__" included, is a key of its own. */
123
+ function topCounts(counts: Map<string, number>): Record<string, number> {
124
+ const sorted = [...counts].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
125
+ const entries: Array<[string, number]> = sorted.slice(0, MAX_KEYS);
126
+ const rest = sorted.slice(MAX_KEYS).reduce((sum, [, n]) => sum + n, 0);
127
+ if (rest > 0) entries.push(['(others)', rest]);
128
+ return Object.fromEntries(entries);
129
+ }
130
+
131
+ function shown(row: SuppressionStatsRow, record: { eventType?: string; source?: string } | undefined): SuppressedEventShown {
132
+ const md = row.metadata ?? {};
133
+ const source = sourceOf(row, record);
134
+ const channel = typeof md.channelId === 'string' && md.channelId !== '' ? md.channelId : undefined;
135
+ const author = authorOf(md);
136
+ return {
137
+ id: row.id,
138
+ at: isoOf(msOf(row.timestamp)),
139
+ eventType: record?.eventType ?? 'unknown',
140
+ ...(source ? { source } : {}),
141
+ ...(channel ? { channel } : {}),
142
+ ...(author ? { author } : {}),
143
+ preview: previewOf(textOf(row)),
144
+ };
145
+ }
146
+
147
+ /**
148
+ * The stats for `rows` (a message slot, oldest first), with every suppress
149
+ * rule in force listed even where it has hidden nothing yet. Rules in force
150
+ * come first, in gate.json's order; rules no longer in force follow, the
151
+ * most recently active first.
152
+ */
153
+ export function suppressionStats(
154
+ rows: Iterable<SuppressionStatsRow>,
155
+ inForce: readonly SuppressRuleInForce[],
156
+ opts: SuppressionStatsOptions = {},
157
+ ): SuppressionStats {
158
+ const recentWanted = Math.max(0, Math.min(MAX_RECENT, Math.floor(opts.recent ?? DEFAULT_RECENT)));
159
+ interface Tally {
160
+ suppressed: number; characters: number; first: number; last: number;
161
+ bySource: Map<string, number>; byEventType: Map<string, number>;
162
+ recent: Array<{ row: SuppressionStatsRow; record: { eventType?: string; source?: string } }>;
163
+ }
164
+ const tallies = new Map<string, Tally>();
165
+ let total = 0;
166
+ let totalCharacters = 0;
167
+ for (const row of rows) {
168
+ const record = suppressionRecordOf(row.metadata);
169
+ if (!record) continue;
170
+ if (opts.rule !== undefined && record.rule !== opts.rule) continue;
171
+ const at = msOf(row.timestamp);
172
+ if (opts.sinceMs !== undefined && !(at >= opts.sinceMs)) continue;
173
+ let tally = tallies.get(record.rule);
174
+ if (!tally) {
175
+ tally = { suppressed: 0, characters: 0, first: at, last: at, bySource: new Map(), byEventType: new Map(), recent: [] };
176
+ tallies.set(record.rule, tally);
177
+ }
178
+ const characters = [...textOf(row)].length;
179
+ tally.suppressed++;
180
+ tally.characters += characters;
181
+ if (at < tally.first) tally.first = at;
182
+ if (at > tally.last) tally.last = at;
183
+ const source = sourceOf(row, record) ?? '(none)';
184
+ tally.bySource.set(source, (tally.bySource.get(source) ?? 0) + 1);
185
+ const eventType = record.eventType ?? 'unknown';
186
+ tally.byEventType.set(eventType, (tally.byEventType.get(eventType) ?? 0) + 1);
187
+ if (recentWanted > 0) {
188
+ tally.recent.push({ row, record });
189
+ if (tally.recent.length > recentWanted) tally.recent.shift();
190
+ }
191
+ total++;
192
+ totalCharacters += characters;
193
+ }
194
+
195
+ const ruleStats = (name: string, rule: SuppressRuleInForce | undefined): RuleSuppressionStats => {
196
+ const tally = tallies.get(name);
197
+ return {
198
+ rule: name,
199
+ inForce: rule !== undefined,
200
+ ...(rule !== undefined ? { sinceStart: rule.sinceStart } : {}),
201
+ suppressed: tally?.suppressed ?? 0,
202
+ characters: tally?.characters ?? 0,
203
+ ...(tally ? { first: isoOf(tally.first), last: isoOf(tally.last) } : {}),
204
+ bySource: tally ? topCounts(tally.bySource) : {},
205
+ byEventType: tally ? topCounts(tally.byEventType) : {},
206
+ recent: tally ? tally.recent.slice().reverse().map(({ row, record }) => shown(row, record)) : [],
207
+ };
208
+ };
209
+
210
+ const listed = new Set<string>();
211
+ const out: RuleSuppressionStats[] = [];
212
+ for (const rule of inForce) {
213
+ if (listed.has(rule.name) || (opts.rule !== undefined && rule.name !== opts.rule)) continue;
214
+ listed.add(rule.name);
215
+ out.push(ruleStats(rule.name, rule));
216
+ }
217
+ const gone = [...tallies.keys()].filter((name) => !listed.has(name))
218
+ .sort((a, b) => tallies.get(b)!.last - tallies.get(a)!.last || a.localeCompare(b));
219
+ for (const name of gone) out.push(ruleStats(name, undefined));
220
+ return { rules: out, total, totalCharacters };
221
+ }
222
+
223
+ /** One suppressed row, as a dump or the review reads it: its text in full, any other block named. */
224
+ export interface SuppressedEventRecord {
225
+ id: string;
226
+ /** ISO 8601. */
227
+ at: string;
228
+ rule: string;
229
+ eventType: string;
230
+ source?: string;
231
+ channel?: string;
232
+ author?: string;
233
+ /** Its text blocks, joined; a block of another kind as `[image]`, `[audio]` and so on. */
234
+ text: string;
235
+ }
236
+
237
+ export interface SuppressedRecordsOptions {
238
+ rule?: string;
239
+ /** Rows stored at or after this time (ms). */
240
+ sinceMs?: number;
241
+ /** Rows stored before this time (ms). */
242
+ untilMs?: number;
243
+ }
244
+
245
+ /** A row's content as text: text blocks as written, any other block named in brackets. */
246
+ function fullTextOf(row: SuppressionStatsRow): string {
247
+ return row.content
248
+ .map((b) => (b.type === 'text' && typeof b.text === 'string' ? b.text : `[${b.type}]`))
249
+ .join('\n');
250
+ }
251
+
252
+ /** The suppressed rows among `rows`, oldest first, their text in full; times are when each was stored. */
253
+ export function suppressedRecords(rows: Iterable<SuppressionStatsRow>, opts: SuppressedRecordsOptions = {}): SuppressedEventRecord[] {
254
+ const out: SuppressedEventRecord[] = [];
255
+ for (const row of rows) {
256
+ const record = suppressionRecordOf(row.metadata);
257
+ if (!record || (opts.rule !== undefined && record.rule !== opts.rule)) continue;
258
+ const at = msOf(row.timestamp);
259
+ if (opts.sinceMs !== undefined && !(at >= opts.sinceMs)) continue;
260
+ if (opts.untilMs !== undefined && !(at < opts.untilMs)) continue;
261
+ const { source, channel, author } = shown(row, record);
262
+ out.push({
263
+ id: row.id,
264
+ at: isoOf(at),
265
+ rule: record.rule,
266
+ eventType: record.eventType ?? 'unknown',
267
+ ...(source ? { source } : {}),
268
+ ...(channel ? { channel } : {}),
269
+ ...(author ? { author } : {}),
270
+ text: fullTextOf(row),
271
+ });
272
+ }
273
+ return out;
274
+ }
275
+
276
+ /** A dump's file formats: one JSON object a line, or a transcript to read. */
277
+ export type SuppressedDumpFormat = 'jsonl' | 'text';
278
+
279
+ /**
280
+ * A dump's file content, at most `maxBytes` (UTF-8): the newest records that
281
+ * fit, oldest first, and how many older ones were left out to fit. A
282
+ * transcript opens with a header naming what it holds (`title`, given how
283
+ * many it holds and the oldest's time) and, when some were left out, how many.
284
+ */
285
+ export function renderSuppressedDump(
286
+ records: readonly SuppressedEventRecord[],
287
+ format: SuppressedDumpFormat,
288
+ opts: { maxBytes: number; title: string | ((written: number, first: string) => string) },
289
+ ): { content: string; written: number; omitted: number } {
290
+ const entry = (r: SuppressedEventRecord): string => format === 'jsonl'
291
+ ? `${JSON.stringify(r)}\n`
292
+ : `[${r.at}] ${r.rule} · ${r.eventType}${r.source ? ` · ${r.source}` : ''}${r.channel ? ` · ${r.channel}` : ''}`
293
+ + `${r.author ? ` · ${r.author}` : ''} · ${r.id}\n${r.text}\n\n`;
294
+ const entries = records.map(entry);
295
+ const titleOf = (from: number): string => typeof opts.title === 'string'
296
+ ? opts.title
297
+ : opts.title(entries.length - from, records[from]?.at ?? '');
298
+ const header = (from: number): string => format === 'jsonl'
299
+ ? ''
300
+ : `# ${titleOf(from)}\n${from > 0 ? `(${from} older events left out to fit)\n` : ''}\n`;
301
+ // The most records, newest first, that fit beside the header they make:
302
+ // the header names how many were left out, so it's sized for each count.
303
+ let bytes = 0;
304
+ let from = entries.length;
305
+ let best = entries.length;
306
+ while (from > 0) {
307
+ bytes += Buffer.byteLength(entries[from - 1]!);
308
+ from--;
309
+ if (Buffer.byteLength(header(from)) + bytes <= opts.maxBytes) best = from;
310
+ else if (bytes > opts.maxBytes) break;
311
+ }
312
+ if (best === entries.length) return { content: '', written: 0, omitted: entries.length };
313
+ return { content: header(best) + entries.slice(best).join(''), written: entries.length - best, omitted: best };
314
+ }
package/src/gate/types.ts CHANGED
@@ -179,6 +179,81 @@ export interface GatePolicy {
179
179
  * fire" has no meaning distinct from the behavior itself.
180
180
  */
181
181
  passthrough?: boolean;
182
+ /**
183
+ * Suppression: an event this rule takes is stored in the chronicle and
184
+ * never rendered into the residents' context. It isn't compiled, injected
185
+ * at a tool boundary, folded into memory, woken for, or counted as the
186
+ * latest inbound. The framework decides this once, as the event arrives
187
+ * (`EventGate.decideSuppression`), and stamps the row
188
+ * (`metadata.suppressed = { rule, eventType }`). The view filters read only
189
+ * that stamp, so changing the rule later never changes what was already
190
+ * rendered or hidden: a removed rule stops suppressing from then on, and
191
+ * the rows it hid stay hidden.
192
+ *
193
+ * The rule needs `behavior: "defer"` (or the legacy `"skip"`), so a release
194
+ * that doesn't know `suppress` defers its events instead of waking the
195
+ * agent on them, and a match that narrows something. Rules stay
196
+ * first-match, with three exceptions: an event addressed to the agent
197
+ * (`chat:addressed`), a heartbeat, and console or API input are never
198
+ * suppressed. The rule lets them by, and the rules after it decide them.
199
+ * A suppress rule decides no wake for an event suppression was asked about
200
+ * (`evaluate` and `probe` pass over it there, `GateEventInfo.suppressionAsked`);
201
+ * where suppression wasn't asked, it defers, as its behavior says.
202
+ */
203
+ suppress?: SuppressConfig;
204
+ }
205
+
206
+ /** A suppress rule's own settings (GatePolicy.suppress). */
207
+ export interface SuppressConfig {
208
+ /**
209
+ * What the rule is for, in the agent's words ("the eye going on and off").
210
+ * Required: the periodic review (SuppressionReviewOptions), where the host
211
+ * runs it, checks a sample of the events the rule hid against it.
212
+ */
213
+ description: string;
214
+ /**
215
+ * The agent's own logic for the rule, when matching isn't enough: the
216
+ * source of a function `(event, state) => boolean`, called for each event
217
+ * the rule's match takes that reaches it (one an earlier rule decides, an
218
+ * addressed one, or the heartbeat's never does), only `true` hiding it. `event` is the event's type,
219
+ * source, channel, author, tags, text, arrival time (`at`, ms) and
220
+ * metadata. `state` is the rule's own JSON object, which the script reads
221
+ * and changes as it likes, kept between events and across restarts
222
+ * (suppress-scripts.ts), and started afresh when the script changes or the
223
+ * rule is removed. So a rule can hide repeats, count, or keep a window of
224
+ * its own. A script that throws or runs past its time counts as saying
225
+ * show: the event goes on to the rules after it. Left out, every event the
226
+ * rule matches is hidden.
227
+ */
228
+ script?: string;
229
+ }
230
+
231
+ /**
232
+ * The periodic review of what suppress rules hid (src/gate/suppression-review.ts):
233
+ * every `intervalMs`, for each rule that hid events since the last review, a
234
+ * small model reads the rule's description and the events it hid (a sample,
235
+ * past `maxEventsPerRule`) and flags any that don't fit the description or
236
+ * that the agent would plausibly need to see. The agent is told what each
237
+ * rule hid and what was flagged, in a `[suppression review]` notice; a
238
+ * flagged event wakes it.
239
+ */
240
+ export interface SuppressionReviewOptions {
241
+ /** False turns the review off. Default true. */
242
+ enabled?: boolean;
243
+ /** How often, in ms. Default 2 hours; at least 10 minutes. */
244
+ intervalMs?: number;
245
+ /** The reviewer's model, as the host's membrane names it. Default `claude-haiku-5-5`. */
246
+ model?: string;
247
+ /** When the agent is told: after every review in which a rule hid something (`"always"`, the default), or only when an event was flagged (`"flagged"`). */
248
+ notify?: 'always' | 'flagged';
249
+ /** Whether a flagged event wakes the agent, which is a full turn of its own model, so a false flag costs that turn. Default true. */
250
+ wakeOnFlag?: boolean;
251
+ /** The most events of one rule the reviewer reads in one review, spread across the period. Default 60. */
252
+ maxEventsPerRule?: number;
253
+ /** How long one rule's review may take, in ms. Default 60,000. */
254
+ timeoutMs?: number;
255
+ /** The reviewer's effort (`output_config.effort`); `"none"` sends none. Default `"low"`. */
256
+ effort?: 'low' | 'medium' | 'high' | 'none';
182
257
  }
183
258
 
184
259
  // ---------------------------------------------------------------------------
@@ -223,6 +298,21 @@ export interface GateOptions {
223
298
  * null), the judge is wired with its defaults; it runs only for `judge`
224
299
  * rules, so a gate with none makes no judge calls. */
225
300
  judge?: ReplyJudgeOptions | null;
301
+ /**
302
+ * The periodic review of what suppress rules hid (SuppressionReviewOptions).
303
+ * On by default wherever a gate is; false turns it off.
304
+ */
305
+ suppressionReview?: SuppressionReviewOptions | false;
306
+ /** How long a suppress rule's script may take for one event before the event is shown (ms). Default 50. */
307
+ suppressScriptTimeoutMs?: number;
308
+ /**
309
+ * Wake onboarding (gate/wake-onboarding.ts): the first event from an MCPL
310
+ * server that the agent's rules let go by without deciding about it brings
311
+ * the agent that server's suggested treatment (MCPL RFC-001 §5), once per
312
+ * declared ontology, with `wake_accept_suggestions` / `wake_decline` to
313
+ * answer. On unless set to false.
314
+ */
315
+ onboarding?: boolean;
226
316
  }
227
317
 
228
318
  /**
@@ -280,6 +370,17 @@ export interface JudgeEventSummary {
280
370
  at: number;
281
371
  }
282
372
 
373
+ /** The kinds of UpcomingWake. */
374
+ export type UpcomingWakeKind =
375
+ | 'heartbeat'
376
+ | 'reminder'
377
+ | 'self-wake'
378
+ | 'sleep-end'
379
+ | 'debounce'
380
+ | 'judge-batch'
381
+ | 'queued'
382
+ | 'provider-hold';
383
+
283
384
  /**
284
385
  * Something that will (or, for a batch going to the judge, may) wake the
285
386
  * agent with no new event: what the judge is told would happen if it answers
@@ -292,6 +393,9 @@ export interface UpcomingWake {
292
393
  certain: boolean;
293
394
  /** What it is, as the judge is told ("its own skip_reply timer"). */
294
395
  what: string;
396
+ /** What it is, in a word that quotes nothing, for healthSnapshot: `what` can
397
+ * quote a reminder or a queue reason. The judge isn't told it. */
398
+ kind?: UpcomingWakeKind;
295
399
  }
296
400
 
297
401
  /** What the gate asks the reply judge for one batch. */
@@ -403,6 +507,20 @@ export interface GateEventInfo {
403
507
  paths?: string[];
404
508
  /** Event tags (MCPL RFC-001), already implication-expanded by the host. */
405
509
  tags?: string[];
510
+ /** The agents admission delivered a targeted event to (its targetAgents).
511
+ * A debounced batch wakes each agent with the events it may see, those
512
+ * naming it and those naming no agent, and agents that may see the same
513
+ * events share one line. Absent for an untargeted event, whose batch wakes
514
+ * every agent getAgentNames lists. */
515
+ recipients?: readonly string[];
516
+ /**
517
+ * Set when the framework asked whether the event is suppressed before it
518
+ * asked about its wake (decideSuppression), so a suppress rule that
519
+ * matches it let it by. Left out, a suppress rule defers the events it
520
+ * matches, as its behavior says: suppression wasn't asked, as for an event
521
+ * that lands where no view hides suppressed rows.
522
+ */
523
+ suppressionAsked?: boolean;
406
524
  }
407
525
 
408
526
  /** An MCPL event whose content the framework stored at arrival, as a
@@ -421,13 +539,13 @@ export interface GateEntryRef {
421
539
  * than wrote: its batch was one event whose entry was still the newest in
422
540
  * the window when the batch fired (EventGate's `isNewestEntry`). A fresh
423
541
  * start of the wake's turn decides it (the framework's placeWakeLines): the
424
- * delivery agent's own start leaves it out while that entry is still the
425
- * newest, and writes it once something follows; a co-resident's start
426
- * places none; two or more such lines are each written;
427
- * and none is written while the host is quiesced or another reader of the
428
- * residents' shared slot has a turn alive. A line left out stays for a
429
- * later fresh start of the same wake. Its text is rendered when it's
430
- * written (wakeLineText), so its age is true then.
542
+ * delivery agent's start, and on a presented window each resident's own
543
+ * start, decides its own line, leaving it out while that entry is still
544
+ * the newest in its window and writing it once something follows; a
545
+ * co-resident whose window isn't presented places none; two or more such
546
+ * lines are each written; and none is written while the host is quiesced.
547
+ * A line left out stays for a later fresh start of the same wake. Its text
548
+ * is rendered when it's written (wakeLineText), so its age is true then.
431
549
  *
432
550
  * Every request for one wake carries the same object: the framework writes
433
551
  * each line once by its identity, across the wake's retries and holds. A
@@ -452,6 +570,15 @@ export interface GatePolicyStats {
452
570
  behavior: GateBehavior;
453
571
  matchCount: number;
454
572
  lastMatchTimestamp: number | null;
573
+ /**
574
+ * Present on a suppress rule: its settings, and with a script, how the
575
+ * script has run since this process started. Its `matchCount` and
576
+ * `lastMatchTimestamp` count the events it took since then: those it
577
+ * suppressed, and those it only deferred where suppression wasn't asked
578
+ * (a channel under conversation routing). `suppression_stats` reads the
579
+ * stamped rows themselves, so its counts cover every start.
580
+ */
581
+ suppress?: SuppressConfig & { scriptStatus?: import('./suppress-scripts.js').SuppressScriptStatus };
455
582
  debounceState?: {
456
583
  pendingCount: number;
457
584
  nextDeliveryMs: number | null;
@@ -504,6 +631,13 @@ export interface GateStatus {
504
631
  * same hazards wake_add_rule warns about at install time.
505
632
  */
506
633
  shadowWarnings: string[];
634
+ /**
635
+ * The first rule that takes MCPL servers' events without naming a server, a
636
+ * channel or a tag (gate/wake-onboarding.ts placementFor): every server
637
+ * connected later is handled by it until a rule for that server goes ahead of
638
+ * it. Absent when there is none.
639
+ */
640
+ catchAll?: { rule: string; note: string };
507
641
  /** Total events the gate has evaluated since startup. */
508
642
  totalEvaluations: number;
509
643
  /**