@animalabs/agent-framework 0.15.0 → 0.16.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 (96) hide show
  1. package/README.md +22 -1
  2. package/dist/src/agent.d.ts +32 -0
  3. package/dist/src/agent.d.ts.map +1 -1
  4. package/dist/src/agent.js +55 -0
  5. package/dist/src/agent.js.map +1 -1
  6. package/dist/src/api/server.d.ts +9 -0
  7. package/dist/src/api/server.d.ts.map +1 -1
  8. package/dist/src/api/server.js +156 -0
  9. package/dist/src/api/server.js.map +1 -1
  10. package/dist/src/api/types.d.ts +15 -0
  11. package/dist/src/api/types.d.ts.map +1 -1
  12. package/dist/src/framework.d.ts +418 -3
  13. package/dist/src/framework.d.ts.map +1 -1
  14. package/dist/src/framework.js +1782 -68
  15. package/dist/src/framework.js.map +1 -1
  16. package/dist/src/gate/event-gate.d.ts +16 -0
  17. package/dist/src/gate/event-gate.d.ts.map +1 -1
  18. package/dist/src/gate/event-gate.js +41 -3
  19. package/dist/src/gate/event-gate.js.map +1 -1
  20. package/dist/src/index.d.ts +4 -1
  21. package/dist/src/index.d.ts.map +1 -1
  22. package/dist/src/index.js +3 -1
  23. package/dist/src/index.js.map +1 -1
  24. package/dist/src/mcpl/channel-registry.d.ts +172 -0
  25. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  26. package/dist/src/mcpl/channel-registry.js +444 -2
  27. package/dist/src/mcpl/channel-registry.js.map +1 -1
  28. package/dist/src/mcpl/server-connection.d.ts +9 -0
  29. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  30. package/dist/src/mcpl/server-connection.js +71 -13
  31. package/dist/src/mcpl/server-connection.js.map +1 -1
  32. package/dist/src/modules/history/index.d.ts +89 -13
  33. package/dist/src/modules/history/index.d.ts.map +1 -1
  34. package/dist/src/modules/history/index.js +655 -24
  35. package/dist/src/modules/history/index.js.map +1 -1
  36. package/dist/src/operator-log.d.ts +78 -0
  37. package/dist/src/operator-log.d.ts.map +1 -0
  38. package/dist/src/operator-log.js +102 -0
  39. package/dist/src/operator-log.js.map +1 -0
  40. package/dist/src/recovery/discord-awareness-outbox.d.ts +10 -0
  41. package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
  42. package/dist/src/recovery/discord-awareness-outbox.js +20 -0
  43. package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
  44. package/dist/src/types/framework.d.ts +24 -0
  45. package/dist/src/types/framework.d.ts.map +1 -1
  46. package/dist/src/types/trace.d.ts +36 -0
  47. package/dist/src/types/trace.d.ts.map +1 -1
  48. package/dist/test/agent-runtime-settings.test.js +199 -1
  49. package/dist/test/agent-runtime-settings.test.js.map +1 -1
  50. package/dist/test/api-admin-gate.test.d.ts +2 -0
  51. package/dist/test/api-admin-gate.test.d.ts.map +1 -0
  52. package/dist/test/api-admin-gate.test.js +116 -0
  53. package/dist/test/api-admin-gate.test.js.map +1 -0
  54. package/dist/test/channel-label-history.test.d.ts +2 -0
  55. package/dist/test/channel-label-history.test.d.ts.map +1 -0
  56. package/dist/test/channel-label-history.test.js +424 -0
  57. package/dist/test/channel-label-history.test.js.map +1 -0
  58. package/dist/test/discord-awareness-outbox.test.js +16 -0
  59. package/dist/test/discord-awareness-outbox.test.js.map +1 -1
  60. package/dist/test/event-gate.test.js +44 -0
  61. package/dist/test/event-gate.test.js.map +1 -1
  62. package/dist/test/helpers/quiesce-crash-child.d.ts +2 -0
  63. package/dist/test/helpers/quiesce-crash-child.d.ts.map +1 -0
  64. package/dist/test/helpers/quiesce-crash-child.js +156 -0
  65. package/dist/test/helpers/quiesce-crash-child.js.map +1 -0
  66. package/dist/test/history-module-overview.test.d.ts +2 -0
  67. package/dist/test/history-module-overview.test.d.ts.map +1 -0
  68. package/dist/test/history-module-overview.test.js +711 -0
  69. package/dist/test/history-module-overview.test.js.map +1 -0
  70. package/dist/test/history-module.test.js +146 -0
  71. package/dist/test/history-module.test.js.map +1 -1
  72. package/dist/test/host-quiesce.test.d.ts +2 -0
  73. package/dist/test/host-quiesce.test.d.ts.map +1 -0
  74. package/dist/test/host-quiesce.test.js +863 -0
  75. package/dist/test/host-quiesce.test.js.map +1 -0
  76. package/dist/test/operator-surgery.test.d.ts +2 -0
  77. package/dist/test/operator-surgery.test.d.ts.map +1 -0
  78. package/dist/test/operator-surgery.test.js +398 -0
  79. package/dist/test/operator-surgery.test.js.map +1 -0
  80. package/dist/test/puppet-tool-call.test.js +5 -0
  81. package/dist/test/puppet-tool-call.test.js.map +1 -1
  82. package/dist/tsconfig.tsbuildinfo +1 -1
  83. package/package.json +2 -2
  84. package/src/agent.ts +69 -0
  85. package/src/api/server.ts +159 -2
  86. package/src/api/types.ts +15 -0
  87. package/src/framework.ts +2081 -89
  88. package/src/gate/event-gate.ts +47 -4
  89. package/src/index.ts +5 -1
  90. package/src/mcpl/channel-registry.ts +495 -2
  91. package/src/mcpl/server-connection.ts +73 -13
  92. package/src/modules/history/index.ts +689 -25
  93. package/src/operator-log.ts +132 -0
  94. package/src/recovery/discord-awareness-outbox.ts +19 -0
  95. package/src/types/framework.ts +27 -0
  96. package/src/types/trace.ts +42 -0
@@ -199,6 +199,15 @@ export class McplServerConnection extends EventEmitter {
199
199
  private controlPlaneReady = false;
200
200
  private dataPlaneReady = false;
201
201
  private bufferedEvents: Array<{ event: string; args: unknown[] }> = [];
202
+ /** Cap on buffered responder-less events during a plane pause (issue #122).
203
+ * Held server→host requests are never dropped and may exceed this. */
204
+ private static readonly MAX_BUFFERED_EVENTS = 1000;
205
+ private droppedBufferedEvents = 0;
206
+ /** True while ready()/readyControlPlane() re-emit a buffer snapshot. Items
207
+ * re-buffering mid-flush were already admitted once — applying the cap to
208
+ * them again would evict events that were legally held (the cap targets
209
+ * NEW arrivals during a pause, not churn from control-plane flushes). */
210
+ private flushDepth = 0;
202
211
 
203
212
  // Reconnect state (adapted from Anarchid/agent-framework@mcpl-module-proto)
204
213
  private config: McplServerConfig | null = null;
@@ -250,11 +259,22 @@ export class McplServerConnection extends EventEmitter {
250
259
  this.dataPlaneReady = true;
251
260
  const pending = this.bufferedEvents;
252
261
  this.bufferedEvents = [];
253
- for (const { event, args } of pending) {
254
- // Re-enter the gate for every buffered item. A control handler (notably
255
- // tools/list_changed) may install a newer data-plane barrier while this
256
- // snapshot is being flushed; later data items must observe that pause.
257
- this.emit(event, ...args);
262
+ if (pending.length > 200) {
263
+ console.error(
264
+ `[mcpl] ${this.id}: flushing ${pending.length} buffered event(s) synchronously ` +
265
+ `(long pause backlog${this.droppedBufferedEvents > 0 ? `; ${this.droppedBufferedEvents} dropped at cap` : ''})`,
266
+ );
267
+ }
268
+ this.flushDepth++;
269
+ try {
270
+ for (const { event, args } of pending) {
271
+ // Re-enter the gate for every buffered item. A control handler (notably
272
+ // tools/list_changed) may install a newer data-plane barrier while this
273
+ // snapshot is being flushed; later data items must observe that pause.
274
+ this.emit(event, ...args);
275
+ }
276
+ } finally {
277
+ this.flushDepth--;
258
278
  }
259
279
  }
260
280
 
@@ -268,13 +288,18 @@ export class McplServerConnection extends EventEmitter {
268
288
  this.controlPlaneReady = true;
269
289
  const pending = this.bufferedEvents;
270
290
  this.bufferedEvents = [];
271
- for (const item of pending) {
272
- // Re-enter the ADMISSION gate (this.emit), never super.emit: control
273
- // events include channels-register/changed, which are grant-gated —
274
- // the old direct flush bypassed positive-grant enforcement entirely
275
- // (PR #79 review blocker 3). Data-plane items re-buffer in order via
276
- // the same call, since dataPlaneReady is still false.
277
- this.emit(item.event, ...item.args);
291
+ this.flushDepth++;
292
+ try {
293
+ for (const item of pending) {
294
+ // Re-enter the ADMISSION gate (this.emit), never super.emit: control
295
+ // events include channels-register/changed, which are grant-gated —
296
+ // the old direct flush bypassed positive-grant enforcement entirely
297
+ // (PR #79 review blocker 3). Data-plane items re-buffer in order via
298
+ // the same call, since dataPlaneReady is still false.
299
+ this.emit(item.event, ...item.args);
300
+ }
301
+ } finally {
302
+ this.flushDepth--;
278
303
  }
279
304
  }
280
305
 
@@ -335,6 +360,34 @@ export class McplServerConnection extends EventEmitter {
335
360
  }
336
361
  return super.emit(event, ...args);
337
362
  }
363
+ // Buffer cap (issue #122): a long data-plane pause (quiesce window) on a
364
+ // busy server would otherwise grow this array without bound. Only
365
+ // responder-LESS events are ever dropped — a buffered server→host REQUEST
366
+ // holds a live responder the server is blocking on, and dropping it would
367
+ // strand that call forever (the server timing out and reconnecting is the
368
+ // accepted bound there, and reconnect re-runs the barrier funnel).
369
+ if (this.flushDepth === 0 && this.bufferedEvents.length >= McplServerConnection.MAX_BUFFERED_EVENTS) {
370
+ const hasResponder = (item: { args: unknown[] }): boolean => {
371
+ const responder = item.args[1] as { respond?: unknown } | undefined;
372
+ return typeof responder?.respond === 'function';
373
+ };
374
+ if (!hasResponder({ args })) {
375
+ this.droppedBufferedEvents++;
376
+ if (this.droppedBufferedEvents % 100 === 1) {
377
+ console.error(
378
+ `[mcpl] ${this.id}: buffered-event cap (${McplServerConnection.MAX_BUFFERED_EVENTS}) — ` +
379
+ `dropped ${this.droppedBufferedEvents} responder-less event(s) while the plane is paused`,
380
+ );
381
+ }
382
+ const dropIndex = this.bufferedEvents.findIndex((item) => !hasResponder(item));
383
+ if (dropIndex >= 0) {
384
+ this.bufferedEvents.splice(dropIndex, 1); // drop-oldest non-request
385
+ this.bufferedEvents.push({ event: name, args });
386
+ }
387
+ // else: buffer is all held requests — drop the incoming event instead.
388
+ return true;
389
+ }
390
+ }
338
391
  this.bufferedEvents.push({ event: name, args });
339
392
  return true;
340
393
  }
@@ -359,7 +412,14 @@ export class McplServerConnection extends EventEmitter {
359
412
  || event === 'tools-list-changed'
360
413
  || event === 'connect-failed'
361
414
  || event === 'reconnect-failed'
362
- || event === 'orphaned-response';
415
+ || event === 'orphaned-response'
416
+ // host/command is OPERATOR traffic, not agent-wake traffic: it must
417
+ // stay deliverable while the data plane is paused, or a quiesced host
418
+ // could never receive the `resume` that lifts the pause (issue #122 —
419
+ // the command would buffer behind the very gate it is meant to open).
420
+ // The allowHostCommands authority check lives on the emit pass path
421
+ // below, so promotion does not widen who may issue commands.
422
+ || event === 'host-command';
363
423
  }
364
424
 
365
425
  private static isLifecycleEvent(event: string): boolean {