@animalabs/agent-framework 0.11.0 → 0.13.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 (160) hide show
  1. package/dist/src/agent.d.ts +11 -1
  2. package/dist/src/agent.d.ts.map +1 -1
  3. package/dist/src/agent.js +60 -4
  4. package/dist/src/agent.js.map +1 -1
  5. package/dist/src/framework.d.ts +72 -1
  6. package/dist/src/framework.d.ts.map +1 -1
  7. package/dist/src/framework.js +873 -123
  8. package/dist/src/framework.js.map +1 -1
  9. package/dist/src/gate/event-gate.d.ts +7 -1
  10. package/dist/src/gate/event-gate.d.ts.map +1 -1
  11. package/dist/src/gate/event-gate.js +15 -1
  12. package/dist/src/gate/event-gate.js.map +1 -1
  13. package/dist/src/kv-unified-wire.d.ts +16 -0
  14. package/dist/src/kv-unified-wire.d.ts.map +1 -0
  15. package/dist/src/kv-unified-wire.js +2 -0
  16. package/dist/src/kv-unified-wire.js.map +1 -0
  17. package/dist/src/mcpl/channel-registry.d.ts +84 -1
  18. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  19. package/dist/src/mcpl/channel-registry.js +188 -5
  20. package/dist/src/mcpl/channel-registry.js.map +1 -1
  21. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  22. package/dist/src/mcpl/hook-orchestrator.js +28 -2
  23. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  24. package/dist/src/mcpl/push-handler.d.ts +6 -1
  25. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  26. package/dist/src/mcpl/push-handler.js +29 -2
  27. package/dist/src/mcpl/push-handler.js.map +1 -1
  28. package/dist/src/mcpl/reference-fetcher.d.ts +59 -0
  29. package/dist/src/mcpl/reference-fetcher.d.ts.map +1 -0
  30. package/dist/src/mcpl/reference-fetcher.js +240 -0
  31. package/dist/src/mcpl/reference-fetcher.js.map +1 -0
  32. package/dist/src/mcpl/references.d.ts +125 -0
  33. package/dist/src/mcpl/references.d.ts.map +1 -0
  34. package/dist/src/mcpl/references.js +262 -0
  35. package/dist/src/mcpl/references.js.map +1 -0
  36. package/dist/src/mcpl/types.d.ts +32 -0
  37. package/dist/src/mcpl/types.d.ts.map +1 -1
  38. package/dist/src/mcpl/types.js.map +1 -1
  39. package/dist/src/modules/index.d.ts +2 -1
  40. package/dist/src/modules/index.d.ts.map +1 -1
  41. package/dist/src/modules/index.js +1 -1
  42. package/dist/src/modules/index.js.map +1 -1
  43. package/dist/src/modules/workspace/index.d.ts +125 -2
  44. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  45. package/dist/src/modules/workspace/index.js +354 -58
  46. package/dist/src/modules/workspace/index.js.map +1 -1
  47. package/dist/src/modules/workspace/types.d.ts +7 -0
  48. package/dist/src/modules/workspace/types.d.ts.map +1 -1
  49. package/dist/src/modules/workspace/types.js.map +1 -1
  50. package/dist/src/tool-result-history.d.ts.map +1 -1
  51. package/dist/src/tool-result-history.js +14 -0
  52. package/dist/src/tool-result-history.js.map +1 -1
  53. package/dist/src/tool-wrapper-prose-guard.d.ts +8 -0
  54. package/dist/src/tool-wrapper-prose-guard.d.ts.map +1 -0
  55. package/dist/src/tool-wrapper-prose-guard.js +37 -0
  56. package/dist/src/tool-wrapper-prose-guard.js.map +1 -0
  57. package/dist/src/tune-out/coordinator.d.ts +144 -0
  58. package/dist/src/tune-out/coordinator.d.ts.map +1 -0
  59. package/dist/src/tune-out/coordinator.js +444 -0
  60. package/dist/src/tune-out/coordinator.js.map +1 -0
  61. package/dist/src/tune-out/tools.d.ts +43 -0
  62. package/dist/src/tune-out/tools.d.ts.map +1 -0
  63. package/dist/src/tune-out/tools.js +93 -0
  64. package/dist/src/tune-out/tools.js.map +1 -0
  65. package/dist/src/types/agent.d.ts +15 -0
  66. package/dist/src/types/agent.d.ts.map +1 -1
  67. package/dist/src/types/framework.d.ts +6 -0
  68. package/dist/src/types/framework.d.ts.map +1 -1
  69. package/dist/src/usage-accounting.d.ts +5 -0
  70. package/dist/src/usage-accounting.d.ts.map +1 -0
  71. package/dist/src/usage-accounting.js +11 -0
  72. package/dist/src/usage-accounting.js.map +1 -0
  73. package/dist/test/channel-incoming-targeting.test.d.ts +2 -0
  74. package/dist/test/channel-incoming-targeting.test.d.ts.map +1 -0
  75. package/dist/test/channel-incoming-targeting.test.js +81 -0
  76. package/dist/test/channel-incoming-targeting.test.js.map +1 -0
  77. package/dist/test/conversation-routing.test.js +39 -0
  78. package/dist/test/conversation-routing.test.js.map +1 -1
  79. package/dist/test/kv-unified-wiring.test.d.ts +2 -0
  80. package/dist/test/kv-unified-wiring.test.d.ts.map +1 -0
  81. package/dist/test/kv-unified-wiring.test.js +56 -0
  82. package/dist/test/kv-unified-wiring.test.js.map +1 -0
  83. package/dist/test/mcpl-push-convert.test.d.ts +2 -0
  84. package/dist/test/mcpl-push-convert.test.d.ts.map +1 -0
  85. package/dist/test/mcpl-push-convert.test.js +42 -0
  86. package/dist/test/mcpl-push-convert.test.js.map +1 -0
  87. package/dist/test/mcpl-reference-fetcher.test.d.ts +2 -0
  88. package/dist/test/mcpl-reference-fetcher.test.d.ts.map +1 -0
  89. package/dist/test/mcpl-reference-fetcher.test.js +199 -0
  90. package/dist/test/mcpl-reference-fetcher.test.js.map +1 -0
  91. package/dist/test/per-agent-delivery.test.d.ts +2 -0
  92. package/dist/test/per-agent-delivery.test.d.ts.map +1 -0
  93. package/dist/test/per-agent-delivery.test.js +108 -0
  94. package/dist/test/per-agent-delivery.test.js.map +1 -0
  95. package/dist/test/present-while-acting.test.js +64 -0
  96. package/dist/test/present-while-acting.test.js.map +1 -1
  97. package/dist/test/tool-result-history.test.js +78 -0
  98. package/dist/test/tool-result-history.test.js.map +1 -1
  99. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts +2 -0
  100. package/dist/test/tool-wrapper-prose-guard-integration.test.d.ts.map +1 -0
  101. package/dist/test/tool-wrapper-prose-guard-integration.test.js +485 -0
  102. package/dist/test/tool-wrapper-prose-guard-integration.test.js.map +1 -0
  103. package/dist/test/tool-wrapper-prose-guard.test.d.ts +2 -0
  104. package/dist/test/tool-wrapper-prose-guard.test.d.ts.map +1 -0
  105. package/dist/test/tool-wrapper-prose-guard.test.js +34 -0
  106. package/dist/test/tool-wrapper-prose-guard.test.js.map +1 -0
  107. package/dist/test/tune-out-e2e.test.d.ts +2 -0
  108. package/dist/test/tune-out-e2e.test.d.ts.map +1 -0
  109. package/dist/test/tune-out-e2e.test.js +142 -0
  110. package/dist/test/tune-out-e2e.test.js.map +1 -0
  111. package/dist/test/tune-out-gate-composition.test.d.ts +2 -0
  112. package/dist/test/tune-out-gate-composition.test.d.ts.map +1 -0
  113. package/dist/test/tune-out-gate-composition.test.js +221 -0
  114. package/dist/test/tune-out-gate-composition.test.js.map +1 -0
  115. package/dist/test/tune-out-lifecycle.test.d.ts +2 -0
  116. package/dist/test/tune-out-lifecycle.test.d.ts.map +1 -0
  117. package/dist/test/tune-out-lifecycle.test.js +121 -0
  118. package/dist/test/tune-out-lifecycle.test.js.map +1 -0
  119. package/dist/test/turn-trigger-provenance.test.d.ts +2 -0
  120. package/dist/test/turn-trigger-provenance.test.d.ts.map +1 -0
  121. package/dist/test/turn-trigger-provenance.test.js +85 -0
  122. package/dist/test/turn-trigger-provenance.test.js.map +1 -0
  123. package/dist/test/usage-accounting.test.d.ts +2 -0
  124. package/dist/test/usage-accounting.test.d.ts.map +1 -0
  125. package/dist/test/usage-accounting.test.js +13 -0
  126. package/dist/test/usage-accounting.test.js.map +1 -0
  127. package/dist/test/workspace-branch-continuation.test.d.ts +15 -0
  128. package/dist/test/workspace-branch-continuation.test.d.ts.map +1 -0
  129. package/dist/test/workspace-branch-continuation.test.js +183 -0
  130. package/dist/test/workspace-branch-continuation.test.js.map +1 -0
  131. package/dist/test/workspace-read-file-from-disk.test.d.ts +17 -0
  132. package/dist/test/workspace-read-file-from-disk.test.d.ts.map +1 -0
  133. package/dist/test/workspace-read-file-from-disk.test.js +174 -0
  134. package/dist/test/workspace-read-file-from-disk.test.js.map +1 -0
  135. package/dist/test/workspace-read-image.test.js +8 -0
  136. package/dist/test/workspace-read-image.test.js.map +1 -1
  137. package/dist/test/workspace-restore-order.test.js +1 -1
  138. package/dist/test/workspace-restore-order.test.js.map +1 -1
  139. package/dist/tsconfig.tsbuildinfo +1 -1
  140. package/package.json +2 -2
  141. package/src/agent.ts +77 -4
  142. package/src/framework.ts +953 -128
  143. package/src/gate/event-gate.ts +28 -3
  144. package/src/kv-unified-wire.ts +12 -0
  145. package/src/mcpl/channel-registry.ts +264 -8
  146. package/src/mcpl/hook-orchestrator.ts +29 -2
  147. package/src/mcpl/push-handler.ts +30 -2
  148. package/src/mcpl/reference-fetcher.ts +240 -0
  149. package/src/mcpl/references.ts +292 -0
  150. package/src/mcpl/types.ts +29 -0
  151. package/src/modules/index.ts +2 -1
  152. package/src/modules/workspace/index.ts +425 -66
  153. package/src/modules/workspace/types.ts +7 -0
  154. package/src/tool-result-history.ts +11 -0
  155. package/src/tool-wrapper-prose-guard.ts +38 -0
  156. package/src/tune-out/coordinator.ts +548 -0
  157. package/src/tune-out/tools.ts +129 -0
  158. package/src/types/agent.ts +16 -0
  159. package/src/types/framework.ts +7 -0
  160. package/src/usage-accounting.ts +8 -0
@@ -0,0 +1,34 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { detectKnownToolWrapperProse } from '../src/tool-wrapper-prose-guard.js';
4
+ const known = new Set(['mcpl--heartbeat--heartbeat_status', 'skip_reply']);
5
+ const text = (value) => [{ type: 'text', text: value }];
6
+ test('whole-response known-tool prose guard: matches paired heartbeat wrapper', () => {
7
+ assert.equal(detectKnownToolWrapperProse(text('<mcpl--heartbeat--heartbeat_status>\n</mcpl--heartbeat--heartbeat_status>'), known), 'mcpl--heartbeat--heartbeat_status');
8
+ });
9
+ test('whole-response known-tool prose guard: matches self-closing and legacy invoke wrappers', () => {
10
+ assert.equal(detectKnownToolWrapperProse(text('<skip_reply/>'), known), 'skip_reply');
11
+ assert.equal(detectKnownToolWrapperProse(text('<invoke><tool name="skip_reply"/></invoke>'), known), 'skip_reply');
12
+ assert.equal(detectKnownToolWrapperProse(text('<invoke><tool name="skip_reply"><parameter name="reason">quiet</parameter></tool></invoke>'), known), 'skip_reply');
13
+ });
14
+ test('whole-response known-tool prose guard: allows signed thinking before only visible wrapper', () => {
15
+ const blocks = [{ type: 'thinking', thinking: 'private', signature: 'sig' }, ...text('<skip_reply/>')];
16
+ assert.equal(detectKnownToolWrapperProse(blocks, known), 'skip_reply');
17
+ });
18
+ test('whole-response known-tool prose guard: unknown tools do not match', () => {
19
+ assert.equal(detectKnownToolWrapperProse(text('<delete_everything/>'), known), null);
20
+ });
21
+ test('whole-response known-tool prose guard: quotation, code, mixed prose, and partial wrappers do not match', () => {
22
+ for (const value of ['The form `<skip_reply/>` is malformed.', '```xml\n<skip_reply/>\n```', '<skip_reply/>\nI tried to be quiet.', 'prefix <skip_reply/>', '<skip_reply>', '<skip_reply></other>']) {
23
+ assert.equal(detectKnownToolWrapperProse(text(value), known), null, value);
24
+ }
25
+ });
26
+ test('whole-response known-tool prose guard: real structured calls do not match', () => {
27
+ const blocks = [{ type: 'tool_use', id: 't1', name: 'skip_reply', input: {} }];
28
+ assert.equal(detectKnownToolWrapperProse(blocks, known), null);
29
+ });
30
+ test('whole-response known-tool prose guard: empty prose and empty surface do not match', () => {
31
+ assert.equal(detectKnownToolWrapperProse(text(' '), known), null);
32
+ assert.equal(detectKnownToolWrapperProse(text('<skip_reply/>'), new Set()), null);
33
+ });
34
+ //# sourceMappingURL=tool-wrapper-prose-guard.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-wrapper-prose-guard.test.js","sourceRoot":"","sources":["../../test/tool-wrapper-prose-guard.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AAExC,OAAO,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAC;AAEjF,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,mCAAmC,EAAE,YAAY,CAAC,CAAC,CAAC;AAC3E,MAAM,IAAI,GAAG,CAAC,KAAa,EAAkB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAEhF,IAAI,CAAC,yEAAyE,EAAE,GAAG,EAAE;IACnF,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,IAAI,CAAC,2EAA2E,CAAC,EAAE,KAAK,CAAC,EAAE,mCAAmC,CAAC,CAAC;AAC3K,CAAC,CAAC,CAAC;AACH,IAAI,CAAC,wFAAwF,EAAE,GAAG,EAAE;IAClG,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC;IACtF,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,IAAI,CAAC,4CAA4C,CAAC,EAAE,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC;IACnH,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,IAAI,CAAC,4FAA4F,CAAC,EAAE,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC;AACrK,CAAC,CAAC,CAAC;AACH,IAAI,CAAC,2FAA2F,EAAE,GAAG,EAAE;IACrG,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,GAAG,IAAI,CAAC,eAAe,CAAC,CAAmB,CAAC;IACzH,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,YAAY,CAAC,CAAC;AACzE,CAAC,CAAC,CAAC;AACH,IAAI,CAAC,mEAAmE,EAAE,GAAG,EAAE;IAC7E,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,IAAI,CAAC,sBAAsB,CAAC,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;AACvF,CAAC,CAAC,CAAC;AACH,IAAI,CAAC,wGAAwG,EAAE,GAAG,EAAE;IAClH,KAAK,MAAM,KAAK,IAAI,CAAC,wCAAwC,EAAC,4BAA4B,EAAC,qCAAqC,EAAC,sBAAsB,EAAC,cAAc,EAAC,sBAAsB,CAAC,EAAE,CAAC;QAC/L,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC,CAAC,CAAC;AACH,IAAI,CAAC,2EAA2E,EAAE,GAAG,EAAE;IACrF,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,EAAE,CAAmB,CAAC;IACjG,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;AACjE,CAAC,CAAC,CAAC;AACH,IAAI,CAAC,mFAAmF,EAAE,GAAG,EAAE;IAC7F,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;IACnE,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,IAAI,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;AACpF,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tune-out-e2e.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tune-out-e2e.test.d.ts","sourceRoot":"","sources":["../../test/tune-out-e2e.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Tune-out end-to-end (issue #77): a real MCPL channel diverts to the
3
+ * subconscious and comes back.
4
+ *
5
+ * enter → ambient traffic is stamped + stored + never wakes the resident,
6
+ * and never enters their compiled view (but does enter the subconscious's
7
+ * merged view) → an addressed message gets the deterministic
8
+ * suppressed-mention acknowledge AND a coalesced subconscious wake turn →
9
+ * exceeding max-wakes auto-cancels: the resident receives the
10
+ * system-framed <tuned-out-backlog> dump (capped) and one wake, with the
11
+ * raw stamped originals still excluded from their view.
12
+ */
13
+ import { describe, it, before, after } from 'node:test';
14
+ import assert from 'node:assert/strict';
15
+ import { tmpdir } from 'node:os';
16
+ import { mkdtempSync, rmSync, appendFileSync, readFileSync, existsSync, writeFileSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ import { AgentFramework } from '../src/index.js';
19
+ import { MockMembrane, createMockResponse } from './helpers/mock-membrane.js';
20
+ const FIXTURE = join(import.meta.dirname, 'fixtures/tune-out-mcpl-server.mjs');
21
+ const CHANNEL = 'disc:guild:noisy';
22
+ function internals(framework) {
23
+ return framework;
24
+ }
25
+ async function waitFor(cond, what, timeoutMs = 15_000) {
26
+ const deadline = Date.now() + timeoutMs;
27
+ while (Date.now() < deadline) {
28
+ if (await cond())
29
+ return;
30
+ await new Promise((r) => setTimeout(r, 100));
31
+ }
32
+ throw new Error(`timed out waiting for: ${what}`);
33
+ }
34
+ const textOf = (m) => m.content.filter((b) => b.type === 'text').map((b) => b.text ?? '').join('\n');
35
+ describe('tune-out end to end', () => {
36
+ let tempDir;
37
+ let membrane;
38
+ let framework;
39
+ let statusPath;
40
+ let commandPath;
41
+ before(async () => {
42
+ tempDir = mkdtempSync(join(tmpdir(), 'tune-out-e2e-'));
43
+ statusPath = join(tempDir, 'status.jsonl');
44
+ commandPath = join(tempDir, 'commands.txt');
45
+ writeFileSync(commandPath, '');
46
+ membrane = new MockMembrane();
47
+ framework = await AgentFramework.create({
48
+ storePath: join(tempDir, 'test.chronicle'),
49
+ membrane: membrane.asMembrane(),
50
+ agents: [{ name: 'scout', model: 'test-model', systemPrompt: 'You are scout.' }],
51
+ subconscious: {
52
+ enabled: true,
53
+ systemPrompt: 'You are the Subconscious. You watch tuned-out channels for the resident ' +
54
+ 'and report to them in second person, briefly.',
55
+ },
56
+ mcplServers: [{
57
+ id: 'disc',
58
+ command: process.execPath,
59
+ args: [FIXTURE],
60
+ env: { STATUS_PATH: statusPath, COMMAND_PATH: commandPath },
61
+ }],
62
+ modules: [],
63
+ });
64
+ await framework.start();
65
+ await waitFor(() => (internals(framework).channelRegistry?.listChannelsRaw().length ?? 0) > 0, 'channel registration');
66
+ });
67
+ after(async () => {
68
+ await framework.stop();
69
+ rmSync(tempDir, { recursive: true, force: true });
70
+ });
71
+ function emit(kind, id, text) {
72
+ appendFileSync(commandPath, `${kind} ${id} ${text}\n`);
73
+ }
74
+ function statusEvents() {
75
+ if (!existsSync(statusPath))
76
+ return [];
77
+ return readFileSync(statusPath, 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
78
+ }
79
+ it('runs the full divert → wake → auto-cancel arc', async () => {
80
+ const i = internals(framework);
81
+ const coordinator = i.tuneOutCoordinator;
82
+ assert.ok(coordinator, 'coordinator exists when subconscious + mcpl are configured');
83
+ // ---- enter -----------------------------------------------------------
84
+ const entered = coordinator.enter('disc', CHANNEL, {
85
+ cadenceSeconds: 3600, // cadence not exercised here; wake path is
86
+ backlogCap: 2,
87
+ maxWakes: 1,
88
+ }, 'agent-tool');
89
+ assert.ok(entered.ok, 'enter succeeds');
90
+ assert.equal(i.channelRegistry.getDesiredState('disc', CHANNEL), 'tuned-out');
91
+ // The resident's tool board carries tune_out exactly once, and stays
92
+ // that way across calls (getAllTools must not mutate the registry's
93
+ // shared channel-tool array).
94
+ const tuneOutCount = () => framework.getAllTools().filter((t) => t.name === 'tune_out').length;
95
+ assert.equal(tuneOutCount(), 1);
96
+ assert.equal(tuneOutCount(), 1, 'a second call appends nothing');
97
+ assert.equal(tuneOutCount(), 1, 'nor a third');
98
+ // ---- ambient traffic: stamped, stored, no resident wake --------------
99
+ emit('ambient', 'a1', 'release chatter one');
100
+ emit('ambient', 'a2', 'release chatter two');
101
+ emit('ambient', 'a3', 'release chatter three');
102
+ const scout = i.agents.get('scout').getContextManager();
103
+ await waitFor(() => scout.getAllMessages().filter((m) => m.metadata?.tuneOut).length >= 3, 'three stamped diverted messages');
104
+ assert.equal(membrane.calls.length, 0, 'nobody woke for ambient diverted traffic');
105
+ const compiled = await scout.compile();
106
+ assert.ok(!compiled.messages.some((m) => textOf(m).includes('release chatter')), 'diverted messages never enter the resident\'s compiled view');
107
+ const sub = i.agents.get('Subconscious').getContextManager();
108
+ const subCompiled = await sub.compile();
109
+ assert.ok(subCompiled.messages.some((m) => textOf(m).includes('release chatter one')), 'the subconscious\'s merged view includes the diverted backlog');
110
+ // ---- addressed message: ack + coalesced subconscious wake ------------
111
+ membrane.pushResponse(createMockResponse([{ type: 'text', text: 'Noted — nothing urgent yet.' }]));
112
+ emit('addressed', 'm1', 'hey scout, quick question');
113
+ await waitFor(() => statusEvents().some((e) => e.event === 'acknowledge' && e.intent === 'suppressed-tuned-out' && e.messageId === 'm1'), 'deterministic suppressed-mention acknowledge');
114
+ await waitFor(() => membrane.calls.length >= 1, 'subconscious wake turn (coalesced)', 20_000);
115
+ await waitFor(() => (i.channelRegistry.getTuneOutState('disc', CHANNEL)?.wakeCount ?? -1) === 1, 'durable wake count = 1');
116
+ assert.ok(sub.getAllMessages().some((m) => textOf(m).includes('[Tune-out wake:')), 'the wake notice landed in the subconscious\'s own window');
117
+ assert.ok(!scout.getAllMessages().some((m) => textOf(m).includes('[Tune-out wake:')), 'wake notices do not leak into the resident\'s window');
118
+ // ---- second wake exceeds maxWakes=1: auto-cancel ---------------------
119
+ // Two turns follow the cancel: the resident's wake and the
120
+ // subconscious's cancel-report (issue: dump + a message from the
121
+ // subconscious). MockMembrane hands all queued responses to whichever
122
+ // stream starts first, so queue two and assert on durable state only.
123
+ membrane.pushResponse(createMockResponse([{ type: 'text', text: 'ack one' }]));
124
+ membrane.pushResponse(createMockResponse([{ type: 'text', text: 'ack two' }]));
125
+ emit('addressed', 'm2', 'scout are you there?');
126
+ await waitFor(() => i.channelRegistry.getDesiredState('disc', CHANNEL) === 'open', 'auto-cancel returns the channel to open', 20_000);
127
+ await waitFor(() => scout.getAllMessages().some((m) => textOf(m).includes('<tuned-out-backlog')), 'the backlog dump reaches the resident');
128
+ await waitFor(() => sub.getAllMessages().some((m) => textOf(m).includes('[Tune-out cancelled:')), 'the subconscious received its cancel-report notice');
129
+ const dumpMsg = scout.getAllMessages().find((m) => textOf(m).includes('<tuned-out-backlog'));
130
+ const dump = textOf(dumpMsg);
131
+ assert.match(dump, /\[Tune-out: disc:guild:noisy cancelled — wake budget exhausted \(1\), 2 wakes\]/);
132
+ assert.match(dump, /truncated=/, 'backlogCap=2 with 5 diverted messages truncates');
133
+ assert.match(dump, /antra: /, 'dump lines carry author attribution');
134
+ // The stamped originals stay excluded even after cancel; the dump is
135
+ // the delivery (KV-prefix-stable append, not retro-insertion).
136
+ const recompiled = await scout.compile();
137
+ const renderedTexts = recompiled.messages.map(textOf);
138
+ assert.ok(renderedTexts.some((t) => t.includes('<tuned-out-backlog')), 'the dump is visible');
139
+ assert.ok(!renderedTexts.some((t) => t.includes('release chatter one') && !t.includes('<tuned-out-backlog')), 'raw stamped originals stay view-excluded permanently');
140
+ });
141
+ });
142
+ //# sourceMappingURL=tune-out-e2e.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tune-out-e2e.test.js","sourceRoot":"","sources":["../../test/tune-out-e2e.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tune-out-gate-composition.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tune-out-gate-composition.test.d.ts","sourceRoot":"","sources":["../../test/tune-out-gate-composition.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * The resident's wake gate composes into subconscious wakes (#77,
3
+ * antra in #tuneout-talk): the subconscious is affected BY main's gate,
4
+ * it does not replace it. An event the gate suppresses for the resident
5
+ * diverts silently — stamped into the backlog, counted for cadence — but
6
+ * wakes nobody and triggers no suppressed-mention reaction (main would
7
+ * not have signaled either, and reacting would leak a standing mute to
8
+ * its subject).
9
+ */
10
+ import { describe, it } from 'node:test';
11
+ import assert from 'node:assert/strict';
12
+ import { TuneOutCoordinator } from '../src/tune-out/coordinator.js';
13
+ import { CapabilityGrant } from '../src/mcpl/capability-grant.js';
14
+ const PARAMS = {
15
+ epochId: 'e1',
16
+ cadenceSeconds: 3600,
17
+ backlogCap: 10,
18
+ maxWakes: 5,
19
+ startedAtSequence: 1,
20
+ };
21
+ function harness(opts) {
22
+ const wakes = [];
23
+ const acks = [];
24
+ const registry = {
25
+ getTuneOutState: () => ({ params: PARAMS, wakeCount: 0 }),
26
+ recordTuneOutWake: () => ({ params: PARAMS, wakeCount: 1 }),
27
+ listChannelsRaw: () => [],
28
+ };
29
+ const servers = {
30
+ getServer: () => ({
31
+ // The ack path is grant-gated (CapabilityGrant.of reads conn.grant).
32
+ grant: new CapabilityGrant(new Set(['channels.acknowledge']), []),
33
+ sendChannelsAcknowledge: async (p) => {
34
+ acks.push(p.messageId);
35
+ return { acknowledged: true };
36
+ },
37
+ }),
38
+ };
39
+ const hooks = {
40
+ addMessage: () => '',
41
+ requestInference: () => { },
42
+ subconsciousName: () => 'Subconscious',
43
+ primaryName: () => 'scout',
44
+ getStoredMessages: () => [],
45
+ currentSequence: () => 1,
46
+ setSubconsciousAnchor: () => { },
47
+ isForkBound: () => false,
48
+ isPrivilegedAuthor: () => opts?.privileged ?? false,
49
+ allowChannelSpeech: () => false,
50
+ emitTrace: () => { },
51
+ };
52
+ const coordinator = new TuneOutCoordinator(registry, servers, hooks);
53
+ // Observe wake scheduling without waiting out the coalesce timer.
54
+ coordinator
55
+ .scheduleWakeInvocation = (s, c) => { wakes.push(`${s}:${c}`); };
56
+ return { coordinator, wakes, acks };
57
+ }
58
+ describe('duration expiry', () => {
59
+ it('an expired deadline at resume cancels immediately via the standard flow', () => {
60
+ const cancels = [];
61
+ const registry = {
62
+ listChannelsRaw: () => [{ serverId: 'disc', descriptor: { id: '#a' } }],
63
+ getTuneOutState: () => ({
64
+ params: { ...PARAMS, expiresAtMs: Date.now() - 5_000 },
65
+ wakeCount: 0,
66
+ }),
67
+ cancelTuneOut: (_s, c) => {
68
+ cancels.push(c);
69
+ return { params: PARAMS, wakeCount: 0 };
70
+ },
71
+ };
72
+ const hooks = {
73
+ addMessage: () => '',
74
+ requestInference: () => { },
75
+ subconsciousName: () => 'Subconscious',
76
+ primaryName: () => 'scout',
77
+ getStoredMessages: () => [],
78
+ currentSequence: () => 1,
79
+ setSubconsciousAnchor: () => { },
80
+ isForkBound: () => false,
81
+ isPrivilegedAuthor: () => false,
82
+ allowChannelSpeech: () => false,
83
+ emitTrace: () => { },
84
+ };
85
+ const coordinator = new TuneOutCoordinator(registry, {}, hooks);
86
+ coordinator.resumeActiveEpochs();
87
+ assert.deepEqual(cancels, ['#a'], 'expired epoch cancelled at resume');
88
+ coordinator.stop();
89
+ });
90
+ it('a live deadline arms a timer that cancels on schedule', async () => {
91
+ const cancels = [];
92
+ const registry = {
93
+ listChannelsRaw: () => [],
94
+ getTuneOutState: () => null,
95
+ enterTuneOut: () => { },
96
+ cancelTuneOut: (_s, c) => {
97
+ cancels.push(c);
98
+ return { params: PARAMS, wakeCount: 0 };
99
+ },
100
+ };
101
+ const hooks = {
102
+ addMessage: () => '',
103
+ requestInference: () => { },
104
+ subconsciousName: () => 'Subconscious',
105
+ primaryName: () => 'scout',
106
+ getStoredMessages: () => [],
107
+ currentSequence: () => 1,
108
+ setSubconsciousAnchor: () => { },
109
+ isForkBound: () => false,
110
+ isPrivilegedAuthor: () => false,
111
+ allowChannelSpeech: () => false,
112
+ emitTrace: () => { },
113
+ };
114
+ const coordinator = new TuneOutCoordinator(registry, {}, hooks);
115
+ // armExpiry directly with a ~50ms deadline (enter() clamps duration to
116
+ // >=60s, which a unit test should not wait out).
117
+ coordinator.armExpiry('disc', '#a', { ...PARAMS, expiresAtMs: Date.now() + 50 });
118
+ await new Promise((r) => setTimeout(r, 150));
119
+ assert.deepEqual(cancels, ['#a'], 'deadline fired the standard cancel');
120
+ coordinator.stop();
121
+ });
122
+ it('enter() stamps expiresAtMs only when durationSeconds is given', () => {
123
+ const entered = [];
124
+ const registry = {
125
+ listChannelsRaw: () => [],
126
+ getTuneOutState: () => null,
127
+ enterTuneOut: (_s, _c, p) => { entered.push(p); },
128
+ };
129
+ const hooks = {
130
+ addMessage: () => '',
131
+ requestInference: () => { },
132
+ subconsciousName: () => 'Subconscious',
133
+ primaryName: () => 'scout',
134
+ getStoredMessages: () => [],
135
+ currentSequence: () => 42,
136
+ setSubconsciousAnchor: () => { },
137
+ isForkBound: () => false,
138
+ isPrivilegedAuthor: () => false,
139
+ allowChannelSpeech: () => false,
140
+ emitTrace: () => { },
141
+ };
142
+ const coordinator = new TuneOutCoordinator(registry, {}, hooks);
143
+ coordinator.enter('disc', '#a', {}, 'agent-tool');
144
+ assert.equal(entered[0].expiresAtMs, undefined, 'unset by default');
145
+ coordinator.enter('disc', '#b', { durationSeconds: 3600 }, 'agent-tool');
146
+ assert.ok(entered[1].expiresAtMs !== undefined && entered[1].expiresAtMs > Date.now() + 3_500_000, 'deadline stamped from durationSeconds');
147
+ coordinator.stop();
148
+ });
149
+ });
150
+ describe('dispositions injection', () => {
151
+ function dispositionHarness() {
152
+ const registry = {
153
+ listChannelsRaw: () => [],
154
+ getTuneOutState: () => null,
155
+ readCoordinatorState: () => null,
156
+ writeCoordinatorState: (_id, v) => { stateStore = v; },
157
+ };
158
+ let stateStore = null;
159
+ const hooks = {
160
+ addMessage: () => '',
161
+ requestInference: () => { },
162
+ subconsciousName: () => 'Subconscious',
163
+ primaryName: () => 'scout',
164
+ getStoredMessages: () => [],
165
+ currentSequence: () => 1,
166
+ setSubconsciousAnchor: () => { },
167
+ isForkBound: () => false,
168
+ isPrivilegedAuthor: () => false,
169
+ allowChannelSpeech: () => false,
170
+ emitTrace: () => { },
171
+ };
172
+ return new TuneOutCoordinator(registry, {}, hooks);
173
+ }
174
+ it('empty dispositions produce no injection', () => {
175
+ const coordinator = dispositionHarness();
176
+ assert.equal(coordinator.getDispositionsInjection(), null);
177
+ coordinator.stop();
178
+ });
179
+ it('noted dispositions become a system-position injection; empty text deletes', () => {
180
+ const coordinator = dispositionHarness();
181
+ coordinator.noteDisposition('antra', 'pings always escalate');
182
+ coordinator.noteDisposition('release-bot', 'ignore');
183
+ const injection = coordinator.getDispositionsInjection();
184
+ assert.equal(injection?.position, 'system');
185
+ assert.equal(injection?.namespace, 'dispositions');
186
+ assert.match(injection.content[0].text, /antra: pings always escalate/);
187
+ assert.match(injection.content[0].text, /release-bot: ignore/);
188
+ coordinator.noteDisposition('release-bot', '');
189
+ const after = coordinator.getDispositionsInjection();
190
+ assert.ok(!after.content[0].text.includes('release-bot'), 'empty text deletes the key');
191
+ coordinator.stop();
192
+ });
193
+ });
194
+ describe('gate composition on subconscious wakes', () => {
195
+ it('gate-passed addressed messages wake and get the reaction', () => {
196
+ const { coordinator, wakes, acks } = harness();
197
+ const divert = coordinator.onIncoming('disc', '#a', 'm1', ['chat:addressed'], 'U1', true);
198
+ assert.equal(divert?.epochId, 'e1', 'still stamped');
199
+ assert.equal(wakes.length, 1);
200
+ assert.deepEqual(acks, ['m1']);
201
+ });
202
+ it('gate-suppressed addressed messages divert silently: stamp yes, wake no, reaction no', () => {
203
+ const { coordinator, wakes, acks } = harness();
204
+ const divert = coordinator.onIncoming('disc', '#a', 'm2', ['chat:addressed'], 'U1', false);
205
+ assert.equal(divert?.epochId, 'e1', 'still stamped into the backlog');
206
+ assert.equal(wakes.length, 0, 'no subconscious wake past the gate');
207
+ assert.equal(acks.length, 0, 'no reaction — it would leak the mute');
208
+ });
209
+ it('gate-suppressed privileged authors do not wake either (gate is a true precondition)', () => {
210
+ const { coordinator, wakes } = harness({ privileged: true });
211
+ coordinator.onIncoming('disc', '#a', 'm3', ['chat:ambient'], 'U-priv', false);
212
+ assert.equal(wakes.length, 0);
213
+ });
214
+ it('gate-passed privileged ambient authors wake without a reaction (nothing was suppressed toward them)', () => {
215
+ const { coordinator, wakes, acks } = harness({ privileged: true });
216
+ coordinator.onIncoming('disc', '#a', 'm4', ['chat:ambient'], 'U-priv', true);
217
+ assert.equal(wakes.length, 1);
218
+ assert.equal(acks.length, 0);
219
+ });
220
+ });
221
+ //# sourceMappingURL=tune-out-gate-composition.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tune-out-gate-composition.test.js","sourceRoot":"","sources":["../../test/tune-out-gate-composition.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tune-out-lifecycle.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tune-out-lifecycle.test.d.ts","sourceRoot":"","sources":["../../test/tune-out-lifecycle.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Tune-out desired state (issue #77) — durable lifecycle semantics.
3
+ *
4
+ * The third DesiredChannelState. A tuned-out channel stays transport-open
5
+ * (traffic keeps arriving for the subconscious); main's wake/visibility
6
+ * divert happens downstream. Params and wake counts live in the
7
+ * mcpl/channel-lifecycle append-log, replayed last-record-wins, so
8
+ * max-wakes cannot reset on restart and time travel carries the epoch.
9
+ */
10
+ import { test } from 'node:test';
11
+ import assert from 'node:assert/strict';
12
+ import { mkdtempSync, rmSync } from 'node:fs';
13
+ import { tmpdir } from 'node:os';
14
+ import { join } from 'node:path';
15
+ import { JsStore } from '@animalabs/chronicle';
16
+ import { ChannelRegistry } from '../src/mcpl/channel-registry.js';
17
+ function makeRegistry(store) {
18
+ const serverRegistry = {
19
+ getServer: (_id) => null,
20
+ };
21
+ return new ChannelRegistry(serverRegistry, {}, () => { }, () => { }, store ? { store } : undefined);
22
+ }
23
+ const PARAMS = {
24
+ epochId: 'epoch-1',
25
+ cadenceSeconds: 1800,
26
+ backlogCap: 200,
27
+ maxWakes: 5,
28
+ startedAtSequence: 42,
29
+ };
30
+ test('enter/query/cancel round-trip in the projection', () => {
31
+ const registry = makeRegistry();
32
+ registry.enterTuneOut('discord', '#dev', PARAMS, 'agent-tool');
33
+ assert.equal(registry.getDesiredState('discord', '#dev'), 'tuned-out');
34
+ const state = registry.getTuneOutState('discord', '#dev');
35
+ assert.deepEqual(state, { params: PARAMS, wakeCount: 0 });
36
+ const ended = registry.cancelTuneOut('discord', '#dev', 'open', 'agent-tool');
37
+ assert.deepEqual(ended, { params: PARAMS, wakeCount: 0 });
38
+ assert.equal(registry.getDesiredState('discord', '#dev'), 'open');
39
+ assert.equal(registry.getTuneOutState('discord', '#dev'), null);
40
+ // Cancelling twice is a null no-op.
41
+ assert.equal(registry.cancelTuneOut('discord', '#dev', 'open', 'agent-tool'), null);
42
+ });
43
+ test('wake counts are durable across restart; epoch and params replay', () => {
44
+ const dir = mkdtempSync(join(tmpdir(), 'tune-out-lifecycle-'));
45
+ try {
46
+ const store = JsStore.openOrCreate({ path: join(dir, 'store') });
47
+ const first = makeRegistry(store);
48
+ first.enterTuneOut('discord', '#dev', PARAMS, 'agent-tool');
49
+ assert.deepEqual(first.recordTuneOutWake('discord', '#dev'), {
50
+ params: PARAMS,
51
+ wakeCount: 1,
52
+ });
53
+ assert.deepEqual(first.recordTuneOutWake('discord', '#dev'), {
54
+ params: PARAMS,
55
+ wakeCount: 2,
56
+ });
57
+ // Fresh registry over the same store: full state from replay alone.
58
+ const second = makeRegistry(store);
59
+ assert.equal(second.getDesiredState('discord', '#dev'), 'tuned-out');
60
+ assert.deepEqual(second.getTuneOutState('discord', '#dev'), {
61
+ params: PARAMS,
62
+ wakeCount: 2,
63
+ });
64
+ // A wake recorded after restart continues the durable count.
65
+ assert.equal(second.recordTuneOutWake('discord', '#dev')?.wakeCount, 3);
66
+ }
67
+ finally {
68
+ rmSync(dir, { recursive: true, force: true });
69
+ }
70
+ });
71
+ test('a stale epoch\'s wake records do not leak into a new epoch', () => {
72
+ const dir = mkdtempSync(join(tmpdir(), 'tune-out-epoch-'));
73
+ try {
74
+ const store = JsStore.openOrCreate({ path: join(dir, 'store') });
75
+ const registry = makeRegistry(store);
76
+ registry.enterTuneOut('discord', '#dev', PARAMS, 'agent-tool');
77
+ registry.recordTuneOutWake('discord', '#dev');
78
+ registry.recordTuneOutWake('discord', '#dev');
79
+ // Re-enter under a new epoch (fresh params) without cancelling first.
80
+ registry.enterTuneOut('discord', '#dev', { ...PARAMS, epochId: 'epoch-2', maxWakes: 3 }, 'agent-tool');
81
+ assert.equal(registry.getTuneOutState('discord', '#dev')?.wakeCount, 0);
82
+ // Replay agrees: old epoch's wake records are attributed to epoch-1
83
+ // and skipped once epoch-2's desired-state record supersedes it.
84
+ const replayed = makeRegistry(store);
85
+ assert.deepEqual(replayed.getTuneOutState('discord', '#dev'), {
86
+ params: { ...PARAMS, epochId: 'epoch-2', maxWakes: 3 },
87
+ wakeCount: 0,
88
+ });
89
+ }
90
+ finally {
91
+ rmSync(dir, { recursive: true, force: true });
92
+ }
93
+ });
94
+ test('recordTuneOutWake on a non-tuned-out channel is a null no-op', () => {
95
+ const registry = makeRegistry();
96
+ assert.equal(registry.recordTuneOutWake('discord', '#dev'), null);
97
+ assert.equal(registry.getTuneOutState('discord', '#dev'), null);
98
+ });
99
+ test('malformed tuned-out records (no params) are dropped at replay', () => {
100
+ const dir = mkdtempSync(join(tmpdir(), 'tune-out-malformed-'));
101
+ try {
102
+ const store = JsStore.openOrCreate({ path: join(dir, 'store') });
103
+ // Seed the log the way an old/foreign writer might: a tuned-out
104
+ // desired-state with no params object.
105
+ const first = makeRegistry(store);
106
+ void first; // registers the state slot
107
+ store.appendToStateJson('mcpl/channel-lifecycle', {
108
+ kind: 'desired-state',
109
+ serverId: 'discord',
110
+ channelId: '#dev',
111
+ desired: 'tuned-out',
112
+ timestamp: new Date().toISOString(),
113
+ });
114
+ const replayed = makeRegistry(store);
115
+ assert.equal(replayed.getDesiredState('discord', '#dev'), undefined);
116
+ }
117
+ finally {
118
+ rmSync(dir, { recursive: true, force: true });
119
+ }
120
+ });
121
+ //# sourceMappingURL=tune-out-lifecycle.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tune-out-lifecycle.test.js","sourceRoot":"","sources":["../../test/tune-out-lifecycle.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=turn-trigger-provenance.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-trigger-provenance.test.d.ts","sourceRoot":"","sources":["../../test/turn-trigger-provenance.test.ts"],"names":[],"mappings":""}