@animalabs/agent-framework 0.5.7 → 0.6.2

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 (179) hide show
  1. package/dist/src/agent.d.ts +4 -0
  2. package/dist/src/agent.d.ts.map +1 -1
  3. package/dist/src/agent.js +15 -0
  4. package/dist/src/agent.js.map +1 -1
  5. package/dist/src/framework.d.ts +243 -0
  6. package/dist/src/framework.d.ts.map +1 -1
  7. package/dist/src/framework.js +1378 -114
  8. package/dist/src/framework.js.map +1 -1
  9. package/dist/src/gate/event-gate.d.ts +64 -2
  10. package/dist/src/gate/event-gate.d.ts.map +1 -1
  11. package/dist/src/gate/event-gate.js +212 -12
  12. package/dist/src/gate/event-gate.js.map +1 -1
  13. package/dist/src/gate/gate-script-worker.d.ts +2 -0
  14. package/dist/src/gate/gate-script-worker.d.ts.map +1 -0
  15. package/dist/src/gate/gate-script-worker.js +69 -0
  16. package/dist/src/gate/gate-script-worker.js.map +1 -0
  17. package/dist/src/gate/gate-script.d.ts +44 -0
  18. package/dist/src/gate/gate-script.d.ts.map +1 -0
  19. package/dist/src/gate/gate-script.js +164 -0
  20. package/dist/src/gate/gate-script.js.map +1 -0
  21. package/dist/src/gate/types.d.ts +18 -3
  22. package/dist/src/gate/types.d.ts.map +1 -1
  23. package/dist/src/index.d.ts +3 -0
  24. package/dist/src/index.d.ts.map +1 -1
  25. package/dist/src/index.js +4 -0
  26. package/dist/src/index.js.map +1 -1
  27. package/dist/src/mcpl/channel-registry.d.ts +60 -2
  28. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  29. package/dist/src/mcpl/channel-registry.js +114 -9
  30. package/dist/src/mcpl/channel-registry.js.map +1 -1
  31. package/dist/src/mcpl/checkpoint-manager.d.ts +7 -0
  32. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  33. package/dist/src/mcpl/checkpoint-manager.js +15 -0
  34. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  35. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  36. package/dist/src/mcpl/push-handler.js +5 -0
  37. package/dist/src/mcpl/push-handler.js.map +1 -1
  38. package/dist/src/mcpl/server-connection.d.ts +64 -30
  39. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  40. package/dist/src/mcpl/server-connection.js +247 -210
  41. package/dist/src/mcpl/server-connection.js.map +1 -1
  42. package/dist/src/mcpl/transport.d.ts +98 -0
  43. package/dist/src/mcpl/transport.d.ts.map +1 -0
  44. package/dist/src/mcpl/transport.js +281 -0
  45. package/dist/src/mcpl/transport.js.map +1 -0
  46. package/dist/src/mcpl/types.d.ts +59 -3
  47. package/dist/src/mcpl/types.d.ts.map +1 -1
  48. package/dist/src/mcpl/types.js.map +1 -1
  49. package/dist/src/module-registry.d.ts +17 -1
  50. package/dist/src/module-registry.d.ts.map +1 -1
  51. package/dist/src/module-registry.js +101 -6
  52. package/dist/src/module-registry.js.map +1 -1
  53. package/dist/src/prose-segments.d.ts +18 -0
  54. package/dist/src/prose-segments.d.ts.map +1 -0
  55. package/dist/src/prose-segments.js +39 -0
  56. package/dist/src/prose-segments.js.map +1 -0
  57. package/dist/src/runtime/liveness-watchdog-worker.d.ts +2 -0
  58. package/dist/src/runtime/liveness-watchdog-worker.d.ts.map +1 -0
  59. package/dist/src/runtime/liveness-watchdog-worker.js +57 -0
  60. package/dist/src/runtime/liveness-watchdog-worker.js.map +1 -0
  61. package/dist/src/runtime/liveness-watchdog.d.ts +36 -0
  62. package/dist/src/runtime/liveness-watchdog.d.ts.map +1 -0
  63. package/dist/src/runtime/liveness-watchdog.js +106 -0
  64. package/dist/src/runtime/liveness-watchdog.js.map +1 -0
  65. package/dist/src/types/agent.d.ts +47 -0
  66. package/dist/src/types/agent.d.ts.map +1 -1
  67. package/dist/src/types/framework.d.ts +14 -0
  68. package/dist/src/types/framework.d.ts.map +1 -1
  69. package/dist/src/types/module.d.ts +58 -0
  70. package/dist/src/types/module.d.ts.map +1 -1
  71. package/dist/src/types/trace.d.ts +42 -0
  72. package/dist/src/types/trace.d.ts.map +1 -1
  73. package/dist/test/agent-cache-ttl.test.d.ts +2 -0
  74. package/dist/test/agent-cache-ttl.test.d.ts.map +1 -0
  75. package/dist/test/agent-cache-ttl.test.js +25 -0
  76. package/dist/test/agent-cache-ttl.test.js.map +1 -0
  77. package/dist/test/channel-registry-routing.test.js +108 -1
  78. package/dist/test/channel-registry-routing.test.js.map +1 -1
  79. package/dist/test/checkpoint-attribution.test.d.ts +2 -0
  80. package/dist/test/checkpoint-attribution.test.d.ts.map +1 -0
  81. package/dist/test/checkpoint-attribution.test.js +40 -0
  82. package/dist/test/checkpoint-attribution.test.js.map +1 -0
  83. package/dist/test/conversation-fork-injection-scoping.test.d.ts +2 -0
  84. package/dist/test/conversation-fork-injection-scoping.test.d.ts.map +1 -0
  85. package/dist/test/conversation-fork-injection-scoping.test.js +184 -0
  86. package/dist/test/conversation-fork-injection-scoping.test.js.map +1 -0
  87. package/dist/test/event-gate-policy-mutation.test.d.ts +2 -0
  88. package/dist/test/event-gate-policy-mutation.test.d.ts.map +1 -0
  89. package/dist/test/event-gate-policy-mutation.test.js +137 -0
  90. package/dist/test/event-gate-policy-mutation.test.js.map +1 -0
  91. package/dist/test/event-gate-script.test.d.ts +2 -0
  92. package/dist/test/event-gate-script.test.d.ts.map +1 -0
  93. package/dist/test/event-gate-script.test.js +95 -0
  94. package/dist/test/event-gate-script.test.js.map +1 -0
  95. package/dist/test/event-gate-tags.test.d.ts +2 -0
  96. package/dist/test/event-gate-tags.test.d.ts.map +1 -0
  97. package/dist/test/event-gate-tags.test.js +76 -0
  98. package/dist/test/event-gate-tags.test.js.map +1 -0
  99. package/dist/test/gather-context-timeout.test.d.ts +2 -0
  100. package/dist/test/gather-context-timeout.test.d.ts.map +1 -0
  101. package/dist/test/gather-context-timeout.test.js +120 -0
  102. package/dist/test/gather-context-timeout.test.js.map +1 -0
  103. package/dist/test/inference-exhaustion-rewind.test.d.ts +2 -0
  104. package/dist/test/inference-exhaustion-rewind.test.d.ts.map +1 -0
  105. package/dist/test/inference-exhaustion-rewind.test.js +181 -0
  106. package/dist/test/inference-exhaustion-rewind.test.js.map +1 -0
  107. package/dist/test/inference-failure-observability.test.js +4 -0
  108. package/dist/test/inference-failure-observability.test.js.map +1 -1
  109. package/dist/test/liveness-watchdog.test.d.ts +2 -0
  110. package/dist/test/liveness-watchdog.test.d.ts.map +1 -0
  111. package/dist/test/liveness-watchdog.test.js +78 -0
  112. package/dist/test/liveness-watchdog.test.js.map +1 -0
  113. package/dist/test/mcpl-reconnect-events.test.d.ts +2 -0
  114. package/dist/test/mcpl-reconnect-events.test.d.ts.map +1 -0
  115. package/dist/test/mcpl-reconnect-events.test.js +119 -0
  116. package/dist/test/mcpl-reconnect-events.test.js.map +1 -0
  117. package/dist/test/mcpl-reconnect-reregistration.test.d.ts +2 -0
  118. package/dist/test/mcpl-reconnect-reregistration.test.d.ts.map +1 -0
  119. package/dist/test/mcpl-reconnect-reregistration.test.js +181 -0
  120. package/dist/test/mcpl-reconnect-reregistration.test.js.map +1 -0
  121. package/dist/test/mcpl-request-timeout.test.d.ts +2 -0
  122. package/dist/test/mcpl-request-timeout.test.d.ts.map +1 -0
  123. package/dist/test/mcpl-request-timeout.test.js +123 -0
  124. package/dist/test/mcpl-request-timeout.test.js.map +1 -0
  125. package/dist/test/mcpl-runtime-lifecycle.test.d.ts +7 -0
  126. package/dist/test/mcpl-runtime-lifecycle.test.d.ts.map +1 -0
  127. package/dist/test/mcpl-runtime-lifecycle.test.js +142 -0
  128. package/dist/test/mcpl-runtime-lifecycle.test.js.map +1 -0
  129. package/dist/test/mcpl-ws-transport.test.d.ts +2 -0
  130. package/dist/test/mcpl-ws-transport.test.d.ts.map +1 -0
  131. package/dist/test/mcpl-ws-transport.test.js +155 -0
  132. package/dist/test/mcpl-ws-transport.test.js.map +1 -0
  133. package/dist/test/prose-segments.test.d.ts +2 -0
  134. package/dist/test/prose-segments.test.d.ts.map +1 -0
  135. package/dist/test/prose-segments.test.js +52 -0
  136. package/dist/test/prose-segments.test.js.map +1 -0
  137. package/dist/test/refusal-rewind.test.d.ts +2 -0
  138. package/dist/test/refusal-rewind.test.d.ts.map +1 -0
  139. package/dist/test/refusal-rewind.test.js +108 -0
  140. package/dist/test/refusal-rewind.test.js.map +1 -0
  141. package/dist/test/state-scaling-e2e.test.d.ts +22 -0
  142. package/dist/test/state-scaling-e2e.test.d.ts.map +1 -0
  143. package/dist/test/state-scaling-e2e.test.js +200 -0
  144. package/dist/test/state-scaling-e2e.test.js.map +1 -0
  145. package/dist/test/state-scaling.test.d.ts +12 -0
  146. package/dist/test/state-scaling.test.d.ts.map +1 -0
  147. package/dist/test/state-scaling.test.js +243 -0
  148. package/dist/test/state-scaling.test.js.map +1 -0
  149. package/dist/test/tool-result-content.test.d.ts +2 -0
  150. package/dist/test/tool-result-content.test.d.ts.map +1 -0
  151. package/dist/test/tool-result-content.test.js +37 -0
  152. package/dist/test/tool-result-content.test.js.map +1 -0
  153. package/dist/test/trunk-channel-routing.test.d.ts +19 -0
  154. package/dist/test/trunk-channel-routing.test.d.ts.map +1 -0
  155. package/dist/test/trunk-channel-routing.test.js +175 -0
  156. package/dist/test/trunk-channel-routing.test.js.map +1 -0
  157. package/dist/tsconfig.tsbuildinfo +1 -1
  158. package/package.json +4 -3
  159. package/src/agent.ts +16 -0
  160. package/src/framework.ts +1576 -121
  161. package/src/gate/event-gate.ts +209 -12
  162. package/src/gate/gate-script-worker.ts +73 -0
  163. package/src/gate/gate-script.ts +180 -0
  164. package/src/gate/types.ts +19 -3
  165. package/src/index.ts +10 -0
  166. package/src/mcpl/channel-registry.ts +143 -8
  167. package/src/mcpl/checkpoint-manager.ts +16 -0
  168. package/src/mcpl/push-handler.ts +5 -0
  169. package/src/mcpl/server-connection.ts +262 -227
  170. package/src/mcpl/transport.ts +307 -0
  171. package/src/mcpl/types.ts +68 -3
  172. package/src/module-registry.ts +126 -6
  173. package/src/prose-segments.ts +42 -0
  174. package/src/runtime/liveness-watchdog-worker.ts +66 -0
  175. package/src/runtime/liveness-watchdog.ts +122 -0
  176. package/src/types/agent.ts +48 -0
  177. package/src/types/framework.ts +15 -0
  178. package/src/types/module.ts +65 -0
  179. package/src/types/trace.ts +51 -0
@@ -11,7 +11,7 @@
11
11
  * inference tokens, not whether the agent should know about the event.
12
12
  */
13
13
  import type { ToolDefinition } from '../types/events.js';
14
- import type { GateConfig, GateDecision, GateEventInfo, GateStatus } from './types.js';
14
+ import type { GateConfig, GatePolicy, GateDecision, GateEventInfo, GateStatus } from './types.js';
15
15
  export type { GateConfig, GateOptions, GateDecision, GateEventInfo } from './types.js';
16
16
  interface TraceEventLike {
17
17
  type: string;
@@ -20,6 +20,8 @@ interface TraceEventLike {
20
20
  export declare class EventGate {
21
21
  private configPath;
22
22
  private config;
23
+ /** Optional agent-authored programmable gate (gate.js). */
24
+ private gateScript;
23
25
  private compiledPolicies;
24
26
  private configMtime;
25
27
  private lastReloadCheck;
@@ -41,6 +43,8 @@ export declare class EventGate {
41
43
  private sleepUntil;
42
44
  private sleepNote;
43
45
  private sleepSuppressed;
46
+ private sleepTimer;
47
+ private sleepAgent;
44
48
  private privilegedUsersPath;
45
49
  private privilegedUserIds;
46
50
  private privilegedMtime;
@@ -65,10 +69,51 @@ export declare class EventGate {
65
69
  getAgentNames: () => string[];
66
70
  /** Optional clock — defaults to Date.now. Tests inject for deterministic time. */
67
71
  now?: () => number;
72
+ /** Per-event timeout (ms) for the optional gate.js script. Default 50. */
73
+ scriptTimeoutMs?: number;
68
74
  });
69
75
  private loadConfig;
70
76
  private compilePolicies;
71
77
  private reloadIfChanged;
78
+ /**
79
+ * Add or replace (upsert) a single policy in gate.json and apply it live.
80
+ *
81
+ * The raw policy is validated with the SAME `validatePolicy` used when loading
82
+ * gate.json — an invalid shape throws (nothing is written) and the caller
83
+ * surfaces it as a tool error. On success the policy is persisted to gate.json
84
+ * (so it survives restart and is visible to `workspace--edit _config/gate.json`)
85
+ * and the in-memory config is swapped in immediately, so the rule takes effect
86
+ * without waiting for the throttled mtime reload.
87
+ *
88
+ * The freshest on-disk config is re-read first, so a concurrent operator edit
89
+ * isn't clobbered. A policy with the same `name` is replaced IN PLACE (order
90
+ * preserved); otherwise it is appended, or prepended when `position:'prepend'`
91
+ * — first match wins, so a wake rule that must beat a broad defer/debounce
92
+ * belongs at the front.
93
+ */
94
+ addPolicy(rawPolicy: unknown, options?: {
95
+ position?: 'append' | 'prepend';
96
+ }): GatePolicy;
97
+ /**
98
+ * Remove a policy by name from gate.json and clear its live state. Any pending
99
+ * debounce batch is delivered first (matching reloadIfChanged's removal path,
100
+ * so buffered events aren't silently dropped). Returns false if no policy of
101
+ * that name exists (nothing written). Freshest on-disk config is re-read first
102
+ * (see addPolicy).
103
+ */
104
+ removePolicy(name: string): boolean;
105
+ /**
106
+ * List the current policy names (freshest on-disk view). Cheap helper for
107
+ * tools/UX that want to show what's active without the full status payload.
108
+ */
109
+ listPolicyNames(): string[];
110
+ /**
111
+ * Persist a config to gate.json and apply it in memory together: write the
112
+ * file, swap in the compiled policies, and sync `configMtime` to the new file
113
+ * so `reloadIfChanged` doesn't redundantly re-parse identical content. Throws
114
+ * if the write fails (disk/permission) — callers surface it as a tool error.
115
+ */
116
+ private writeConfig;
72
117
  private matchPolicy;
73
118
  private compiledMatches;
74
119
  evaluate(info: GateEventInfo): GateDecision;
@@ -101,14 +146,31 @@ export declare class EventGate {
101
146
  private deliverEvents;
102
147
  onInferenceStarted(agentName: string): void;
103
148
  onInferenceEnded(agentName: string): void;
149
+ /** Live inference-gating snapshot for diagnostics (e.g. a SIGUSR2 dump).
150
+ * A non-empty `inferring` with a stale timestamp + growing `bufferedEvents`
151
+ * is the signature of the wake-wedge: an agent stuck mid-"inference" so all
152
+ * incoming events buffer and never trigger. */
153
+ inferenceDiagnostics(): {
154
+ inferring: string[];
155
+ bufferedEvents: number;
156
+ sleepUntil: number;
157
+ };
104
158
  /** Called internally when debounce fires during inference. */
105
159
  private bufferForInference;
106
160
  /** Begin (or extend) a sleep window of `seconds`. Returns the wake time. */
107
- setSleep(seconds: number, note?: string): {
161
+ setSleep(seconds: number, note?: string, agentName?: string): {
108
162
  until: number;
109
163
  };
110
164
  /** End sleep immediately. Returns true if the agent was asleep. */
111
165
  clearSleep(): boolean;
166
+ /**
167
+ * Self-wake handler: fired by the sleepTimer when a sleep window elapses.
168
+ * Clears the sleep state and requests an inference so the agent wakes on its
169
+ * own — this is what makes `sleep(N)` a real "wake me in N seconds" rather
170
+ * than just a suppression window. No-op if sleep was already cleared (via
171
+ * `wake`) or re-armed to a later time by a fresh `sleep` call.
172
+ */
173
+ private wakeFromSleep;
112
174
  /** Current sleep state, or null if awake. */
113
175
  getSleepState(): {
114
176
  until: number;
@@ -1 +1 @@
1
- {"version":3,"file":"event-gate.d.ts","sourceRoot":"","sources":["../../../src/gate/event-gate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"event-gate.d.ts","sourceRoot":"","sources":["../../../src/gate/event-gate.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,8 @@
11
11
  * inference tokens, not whether the agent should know about the event.
12
12
  */
13
13
  import { existsSync, readFileSync, statSync, writeFileSync, mkdirSync } from 'node:fs';
14
- import { dirname } from 'node:path';
14
+ import { dirname, join } from 'node:path';
15
+ import { GateScript } from './gate-script.js';
15
16
  // ---------------------------------------------------------------------------
16
17
  // Constants
17
18
  // ---------------------------------------------------------------------------
@@ -99,8 +100,8 @@ function validateConfig(raw) {
99
100
  }
100
101
  const obj = raw;
101
102
  const defaultBehavior = obj.default ?? 'always';
102
- if (defaultBehavior !== 'always' && defaultBehavior !== 'skip') {
103
- throw new Error(`gate.json "default" must be "always" or "skip", got: ${defaultBehavior}`);
103
+ if (defaultBehavior !== 'always' && defaultBehavior !== 'defer' && defaultBehavior !== 'skip') {
104
+ throw new Error(`gate.json "default" must be "always", "defer", or "skip", got: ${defaultBehavior}`);
104
105
  }
105
106
  const policies = [];
106
107
  if (Array.isArray(obj.policies)) {
@@ -120,7 +121,7 @@ function validatePolicy(raw) {
120
121
  }
121
122
  // Validate behavior
122
123
  let behavior;
123
- if (obj.behavior === 'always' || obj.behavior === 'skip') {
124
+ if (obj.behavior === 'always' || obj.behavior === 'defer' || obj.behavior === 'skip') {
124
125
  behavior = obj.behavior;
125
126
  }
126
127
  else if (obj.behavior && typeof obj.behavior === 'object') {
@@ -174,7 +175,7 @@ function validatePolicy(raw) {
174
175
  behavior = { passive_sample: out };
175
176
  }
176
177
  else {
177
- throw new Error(`Policy "${obj.name}": unknown behavior — expected always | skip | { debounce } | { rate_limit } | { passive_sample }`);
178
+ throw new Error(`Policy "${obj.name}": unknown behavior — expected always | defer | skip | { debounce } | { rate_limit } | { passive_sample }`);
178
179
  }
179
180
  }
180
181
  else {
@@ -199,6 +200,21 @@ function validatePolicy(raw) {
199
200
  if (fields.length > 0)
200
201
  match.metadataTrue = fields;
201
202
  }
203
+ const tagList = (v) => {
204
+ if (!Array.isArray(v))
205
+ return undefined;
206
+ const out = v.filter((s) => typeof s === 'string' && s.length > 0);
207
+ return out.length > 0 ? out : undefined;
208
+ };
209
+ const tagsAny = tagList(m.tagsAny);
210
+ if (tagsAny)
211
+ match.tagsAny = tagsAny;
212
+ const tagsAll = tagList(m.tagsAll);
213
+ if (tagsAll)
214
+ match.tagsAll = tagsAll;
215
+ const tagsNone = tagList(m.tagsNone);
216
+ if (tagsNone)
217
+ match.tagsNone = tagsNone;
202
218
  if (m.filter && typeof m.filter === 'object') {
203
219
  const f = m.filter;
204
220
  if ((f.type === 'text' || f.type === 'regex') && typeof f.pattern === 'string') {
@@ -233,6 +249,8 @@ function validatePolicy(raw) {
233
249
  export class EventGate {
234
250
  configPath;
235
251
  config;
252
+ /** Optional agent-authored programmable gate (gate.js). */
253
+ gateScript;
236
254
  compiledPolicies = [];
237
255
  configMtime = 0;
238
256
  lastReloadCheck = 0;
@@ -258,12 +276,16 @@ export class EventGate {
258
276
  defaultTriggered = 0;
259
277
  defaultSkipped = 0;
260
278
  defaultByEventType = new Map();
261
- // Sleep state (set via the `sleep` tool). Passive: evaluate() suppresses
262
- // non-privileged wakes while now() < sleepUntil. No timer — after expiry the
263
- // next event evaluates normally.
279
+ // Sleep state (set via the `sleep` tool). evaluate() suppresses non-privileged
280
+ // wakes while now() < sleepUntil. A self-wake timer (sleepTimer) fires an
281
+ // inference when the window expires, so `sleep(N)` means "wake me in N
282
+ // seconds" rather than merely lifting suppression and waiting for an external
283
+ // event.
264
284
  sleepUntil = 0;
265
285
  sleepNote;
266
286
  sleepSuppressed = 0;
287
+ sleepTimer = null;
288
+ sleepAgent;
267
289
  // Privileged users who may wake the agent through sleep. Hot-reloaded from
268
290
  // privilegedUsersPath on change.
269
291
  privilegedUsersPath;
@@ -332,6 +354,9 @@ export class EventGate {
332
354
  // Load config
333
355
  this.config = this.loadConfig() ?? opts.initialConfig ?? { ...DEFAULT_CONFIG };
334
356
  this.compiledPolicies = this.compilePolicies(this.config);
357
+ // Programmable gate: an optional agent-authored `gate.js` next to gate.json.
358
+ // Absent ⇒ inactive (zero overhead beyond a throttled stat check).
359
+ this.gateScript = new GateScript(join(dirname(this.configPath), 'gate.js'), opts.scriptTimeoutMs ?? 50, this.now);
335
360
  }
336
361
  // =========================================================================
337
362
  // Config loading
@@ -382,6 +407,12 @@ export class EventGate {
382
407
  if (policy.match.pathGlob) {
383
408
  compiled.pathRegex = compileGlob(policy.match.pathGlob);
384
409
  }
410
+ if (policy.match.tagsAny)
411
+ compiled.tagsAnyRe = policy.match.tagsAny.map(compileGlob);
412
+ if (policy.match.tagsAll)
413
+ compiled.tagsAllRe = policy.match.tagsAll.map(compileGlob);
414
+ if (policy.match.tagsNone)
415
+ compiled.tagsNoneRe = policy.match.tagsNone.map(compileGlob);
385
416
  return compiled;
386
417
  });
387
418
  }
@@ -447,6 +478,103 @@ export class EventGate {
447
478
  }
448
479
  }
449
480
  // =========================================================================
481
+ // Runtime policy mutation (backs wake_add_rule / wake_remove_rule)
482
+ // =========================================================================
483
+ /**
484
+ * Add or replace (upsert) a single policy in gate.json and apply it live.
485
+ *
486
+ * The raw policy is validated with the SAME `validatePolicy` used when loading
487
+ * gate.json — an invalid shape throws (nothing is written) and the caller
488
+ * surfaces it as a tool error. On success the policy is persisted to gate.json
489
+ * (so it survives restart and is visible to `workspace--edit _config/gate.json`)
490
+ * and the in-memory config is swapped in immediately, so the rule takes effect
491
+ * without waiting for the throttled mtime reload.
492
+ *
493
+ * The freshest on-disk config is re-read first, so a concurrent operator edit
494
+ * isn't clobbered. A policy with the same `name` is replaced IN PLACE (order
495
+ * preserved); otherwise it is appended, or prepended when `position:'prepend'`
496
+ * — first match wins, so a wake rule that must beat a broad defer/debounce
497
+ * belongs at the front.
498
+ */
499
+ addPolicy(rawPolicy, options) {
500
+ const policy = validatePolicy(rawPolicy);
501
+ const current = this.loadConfig() ?? this.config;
502
+ const policies = [...current.policies];
503
+ const idx = policies.findIndex((p) => p.name === policy.name);
504
+ if (idx >= 0) {
505
+ policies[idx] = policy;
506
+ }
507
+ else if (options?.position === 'prepend') {
508
+ policies.unshift(policy);
509
+ }
510
+ else {
511
+ policies.push(policy);
512
+ }
513
+ this.writeConfig({ ...current, policies });
514
+ this.emitTrace({
515
+ type: 'gate:policy-added',
516
+ configPath: this.configPath,
517
+ policyName: policy.name,
518
+ replaced: idx >= 0,
519
+ timestamp: Date.now(),
520
+ });
521
+ return policy;
522
+ }
523
+ /**
524
+ * Remove a policy by name from gate.json and clear its live state. Any pending
525
+ * debounce batch is delivered first (matching reloadIfChanged's removal path,
526
+ * so buffered events aren't silently dropped). Returns false if no policy of
527
+ * that name exists (nothing written). Freshest on-disk config is re-read first
528
+ * (see addPolicy).
529
+ */
530
+ removePolicy(name) {
531
+ const current = this.loadConfig() ?? this.config;
532
+ if (!current.policies.some((p) => p.name === name)) {
533
+ return false;
534
+ }
535
+ // Deliver any pending debounce batch, then drop timers/buckets/counters.
536
+ this.clearPolicyState(name, { deliverPendingDebounce: true });
537
+ this.stats.delete(name);
538
+ const policies = current.policies.filter((p) => p.name !== name);
539
+ this.writeConfig({ ...current, policies });
540
+ this.emitTrace({
541
+ type: 'gate:policy-removed',
542
+ configPath: this.configPath,
543
+ policyName: name,
544
+ timestamp: Date.now(),
545
+ });
546
+ return true;
547
+ }
548
+ /**
549
+ * List the current policy names (freshest on-disk view). Cheap helper for
550
+ * tools/UX that want to show what's active without the full status payload.
551
+ */
552
+ listPolicyNames() {
553
+ const current = this.loadConfig() ?? this.config;
554
+ return current.policies.map((p) => p.name);
555
+ }
556
+ /**
557
+ * Persist a config to gate.json and apply it in memory together: write the
558
+ * file, swap in the compiled policies, and sync `configMtime` to the new file
559
+ * so `reloadIfChanged` doesn't redundantly re-parse identical content. Throws
560
+ * if the write fails (disk/permission) — callers surface it as a tool error.
561
+ */
562
+ writeConfig(config) {
563
+ mkdirSync(dirname(this.configPath), { recursive: true });
564
+ writeFileSync(this.configPath, JSON.stringify(config, null, 2) + '\n');
565
+ this.config = config;
566
+ this.compiledPolicies = this.compilePolicies(config);
567
+ this.configSource = 'file';
568
+ this.configErrors = [];
569
+ this.lastReloadTimestamp = Date.now();
570
+ try {
571
+ this.configMtime = statSync(this.configPath).mtimeMs;
572
+ }
573
+ catch {
574
+ // A failed stat only risks one redundant reload; keep the old mtime.
575
+ }
576
+ }
577
+ // =========================================================================
450
578
  // Policy matching — first match wins
451
579
  // =========================================================================
452
580
  matchPolicy(info) {
@@ -511,6 +639,18 @@ export class EventGate {
511
639
  if (!anyTrue)
512
640
  return false;
513
641
  }
642
+ // Tag matching (MCPL RFC-001). Patterns are globs; `tags` is the event's
643
+ // (implication-expanded) tag set. A pattern "matches" if it matches ANY tag.
644
+ if (compiled.tagsAnyRe || compiled.tagsAllRe || compiled.tagsNoneRe) {
645
+ const tags = info.tags ?? [];
646
+ const patternHits = (re) => tags.some(t => re.test(t));
647
+ if (compiled.tagsAnyRe && !compiled.tagsAnyRe.some(patternHits))
648
+ return false;
649
+ if (compiled.tagsAllRe && !compiled.tagsAllRe.every(patternHits))
650
+ return false;
651
+ if (compiled.tagsNoneRe && compiled.tagsNoneRe.some(patternHits))
652
+ return false;
653
+ }
514
654
  return true;
515
655
  }
516
656
  // =========================================================================
@@ -545,7 +685,11 @@ export class EventGate {
545
685
  }
546
686
  // Privileged author — fall through to normal policy evaluation.
547
687
  }
548
- const policy = this.matchPolicy(info);
688
+ // Programmable gate (gate.js) takes precedence when present and returns a
689
+ // behavior; null/undefined (inactive, error, or a deliberate pass) falls
690
+ // through to the declarative gate.json policies below.
691
+ const scripted = this.gateScript.evaluate(info);
692
+ const policy = scripted != null ? { name: 'gate.js', match: {}, behavior: scripted } : this.matchPolicy(info);
549
693
  const decision = this.computeDecision(policy, info);
550
694
  this.totalEvaluations++;
551
695
  if (!policy) {
@@ -592,8 +736,9 @@ export class EventGate {
592
736
  source: info.serverId || undefined,
593
737
  timestamp: this.now(),
594
738
  });
595
- if (policy.behavior === 'skip') {
596
- return { trigger: false, policyName: policy.name, behavior: 'skip' };
739
+ if (policy.behavior === 'defer' || policy.behavior === 'skip') {
740
+ // Report the configured name faithfully ('defer' or legacy 'skip').
741
+ return { trigger: false, policyName: policy.name, behavior: policy.behavior };
597
742
  }
598
743
  if (policy.behavior === 'always') {
599
744
  this.applyResets(policy);
@@ -729,6 +874,7 @@ export class EventGate {
729
874
  serverId: metadata.serverId ?? '',
730
875
  channelId: metadata.channelId ?? '',
731
876
  metadata,
877
+ tags: Array.isArray(metadata.tags) ? metadata.tags : undefined,
732
878
  });
733
879
  return decision.trigger;
734
880
  };
@@ -812,6 +958,17 @@ export class EventGate {
812
958
  queueMicrotask(() => this.deliverEvents(events));
813
959
  }
814
960
  }
961
+ /** Live inference-gating snapshot for diagnostics (e.g. a SIGUSR2 dump).
962
+ * A non-empty `inferring` with a stale timestamp + growing `bufferedEvents`
963
+ * is the signature of the wake-wedge: an agent stuck mid-"inference" so all
964
+ * incoming events buffer and never trigger. */
965
+ inferenceDiagnostics() {
966
+ return {
967
+ inferring: [...this.inferring],
968
+ bufferedEvents: this.inferenceBuffer.length,
969
+ sleepUntil: this.sleepUntil,
970
+ };
971
+ }
815
972
  // =========================================================================
816
973
  // Inference buffer cap
817
974
  // =========================================================================
@@ -831,11 +988,18 @@ export class EventGate {
831
988
  // Sleep
832
989
  // =========================================================================
833
990
  /** Begin (or extend) a sleep window of `seconds`. Returns the wake time. */
834
- setSleep(seconds, note) {
991
+ setSleep(seconds, note, agentName) {
835
992
  const ms = Math.max(0, Math.floor(seconds * 1000));
836
993
  this.sleepUntil = this.now() + ms;
837
994
  this.sleepNote = note;
995
+ this.sleepAgent = agentName;
838
996
  this.sleepSuppressed = 0;
997
+ // Arm the self-wake: when the window elapses, clear sleep and request an
998
+ // inference so the agent resumes on its own (not dependent on a heartbeat
999
+ // tick or an incoming message). Cancelled by clearSleep()/`wake`.
1000
+ if (this.sleepTimer)
1001
+ clearTimeout(this.sleepTimer);
1002
+ this.sleepTimer = setTimeout(() => this.wakeFromSleep('sleep-expired'), ms);
839
1003
  this.reloadPrivilegedIfChanged();
840
1004
  this.emitTrace({
841
1005
  type: 'gate:decision',
@@ -852,8 +1016,42 @@ export class EventGate {
852
1016
  const wasAsleep = this.sleepUntil > this.now();
853
1017
  this.sleepUntil = 0;
854
1018
  this.sleepNote = undefined;
1019
+ this.sleepAgent = undefined;
1020
+ if (this.sleepTimer) {
1021
+ clearTimeout(this.sleepTimer);
1022
+ this.sleepTimer = null;
1023
+ }
855
1024
  return wasAsleep;
856
1025
  }
1026
+ /**
1027
+ * Self-wake handler: fired by the sleepTimer when a sleep window elapses.
1028
+ * Clears the sleep state and requests an inference so the agent wakes on its
1029
+ * own — this is what makes `sleep(N)` a real "wake me in N seconds" rather
1030
+ * than just a suppression window. No-op if sleep was already cleared (via
1031
+ * `wake`) or re-armed to a later time by a fresh `sleep` call.
1032
+ */
1033
+ wakeFromSleep(reason) {
1034
+ this.sleepTimer = null;
1035
+ if (this.sleepUntil === 0 || this.sleepUntil > this.now())
1036
+ return;
1037
+ const note = this.sleepNote;
1038
+ const agent = this.sleepAgent;
1039
+ this.sleepUntil = 0;
1040
+ this.sleepNote = undefined;
1041
+ this.sleepAgent = undefined;
1042
+ this.emitTrace({
1043
+ type: 'gate:decision',
1044
+ eventType: 'sleep:expired',
1045
+ matchedPolicy: 'asleep',
1046
+ trigger: true,
1047
+ behavior: 'always',
1048
+ timestamp: this.now(),
1049
+ });
1050
+ const detail = note ? `${reason}: ${note}` : reason;
1051
+ const targets = agent ? [agent] : this.getAgentNamesFn();
1052
+ for (const a of targets)
1053
+ this.requestInferenceFn(a, detail, 'sleep');
1054
+ }
857
1055
  /** Current sleep state, or null if awake. */
858
1056
  getSleepState() {
859
1057
  const remainingMs = this.sleepUntil - this.now();
@@ -976,6 +1174,7 @@ export class EventGate {
976
1174
  configSource: this.configSource,
977
1175
  lastReloadTimestamp: this.lastReloadTimestamp,
978
1176
  default: this.config.default ?? 'always',
1177
+ script: this.gateScript.status(),
979
1178
  policies,
980
1179
  errors: [...this.configErrors],
981
1180
  totalEvaluations: this.totalEvaluations,
@@ -990,6 +1189,7 @@ export class EventGate {
990
1189
  // Cleanup
991
1190
  // =========================================================================
992
1191
  dispose() {
1192
+ this.gateScript.dispose();
993
1193
  for (const state of this.debounceTimers.values()) {
994
1194
  clearTimeout(state.timer);
995
1195
  }