@memnexus-ai/mx-agent-cli 0.1.167 → 0.1.169

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 (46) hide show
  1. package/dist/__tests__/config-command.test.d.ts +9 -0
  2. package/dist/__tests__/config-command.test.d.ts.map +1 -0
  3. package/dist/__tests__/config-command.test.js +55 -0
  4. package/dist/__tests__/config-command.test.js.map +1 -0
  5. package/dist/__tests__/coordination-brief.test.d.ts +9 -0
  6. package/dist/__tests__/coordination-brief.test.d.ts.map +1 -0
  7. package/dist/__tests__/coordination-brief.test.js +95 -0
  8. package/dist/__tests__/coordination-brief.test.js.map +1 -0
  9. package/dist/__tests__/coordination-client.test.d.ts +9 -0
  10. package/dist/__tests__/coordination-client.test.d.ts.map +1 -0
  11. package/dist/__tests__/coordination-client.test.js +82 -0
  12. package/dist/__tests__/coordination-client.test.js.map +1 -0
  13. package/dist/__tests__/coordination-command.test.d.ts +9 -0
  14. package/dist/__tests__/coordination-command.test.d.ts.map +1 -0
  15. package/dist/__tests__/coordination-command.test.js +108 -0
  16. package/dist/__tests__/coordination-command.test.js.map +1 -0
  17. package/dist/__tests__/coordination-fallback.test.d.ts +9 -0
  18. package/dist/__tests__/coordination-fallback.test.d.ts.map +1 -0
  19. package/dist/__tests__/coordination-fallback.test.js +178 -0
  20. package/dist/__tests__/coordination-fallback.test.js.map +1 -0
  21. package/dist/__tests__/coordination-render.test.d.ts +9 -0
  22. package/dist/__tests__/coordination-render.test.d.ts.map +1 -0
  23. package/dist/__tests__/coordination-render.test.js +80 -0
  24. package/dist/__tests__/coordination-render.test.js.map +1 -0
  25. package/dist/commands/config.d.ts.map +1 -1
  26. package/dist/commands/config.js +5 -1
  27. package/dist/commands/config.js.map +1 -1
  28. package/dist/commands/coordination.d.ts +80 -0
  29. package/dist/commands/coordination.d.ts.map +1 -0
  30. package/dist/commands/coordination.js +194 -0
  31. package/dist/commands/coordination.js.map +1 -0
  32. package/dist/index.js +68 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/lib/coordination-client.d.ts +75 -0
  35. package/dist/lib/coordination-client.d.ts.map +1 -0
  36. package/dist/lib/coordination-client.js +88 -0
  37. package/dist/lib/coordination-client.js.map +1 -0
  38. package/dist/lib/coordination-fallback.d.ts +79 -0
  39. package/dist/lib/coordination-fallback.d.ts.map +1 -0
  40. package/dist/lib/coordination-fallback.js +242 -0
  41. package/dist/lib/coordination-fallback.js.map +1 -0
  42. package/dist/lib/coordination-render.d.ts +53 -0
  43. package/dist/lib/coordination-render.d.ts.map +1 -0
  44. package/dist/lib/coordination-render.js +86 -0
  45. package/dist/lib/coordination-render.js.map +1 -0
  46. package/package.json +1 -1
@@ -0,0 +1,9 @@
1
+ /**
2
+ * mx-agent config -- secret masking (BLOCKER-1).
3
+ *
4
+ * The coordination-service-token must be masked on set / get / list; the raw
5
+ * token must never appear in stdout. Uses a mocked config store so the real
6
+ * ~/.mx-agent/config.json is untouched.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=config-command.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-command.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/config-command.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * mx-agent config -- secret masking (BLOCKER-1).
3
+ *
4
+ * The coordination-service-token must be masked on set / get / list; the raw
5
+ * token must never appear in stdout. Uses a mocked config store so the real
6
+ * ~/.mx-agent/config.json is untouched.
7
+ */
8
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
9
+ const store = vi.hoisted(() => ({ data: {} }));
10
+ vi.mock('../lib/config.js', () => ({
11
+ loadConfig: () => ({ ...store.data }),
12
+ saveConfig: (c) => { store.data = { ...c }; },
13
+ getConfigValue: (k) => store.data[k],
14
+ setConfigValue: (k, v) => { store.data[k] = v; },
15
+ deleteConfigValue: (k) => { if (k in store.data) {
16
+ delete store.data[k];
17
+ return true;
18
+ } return false; },
19
+ }));
20
+ import { runConfig } from '../commands/config.js';
21
+ const SECRET = 'coord-tok-DEADBEEF-must-not-leak';
22
+ let logs;
23
+ beforeEach(() => {
24
+ store.data = {};
25
+ logs = [];
26
+ vi.spyOn(console, 'log').mockImplementation((m) => { logs.push(String(m)); });
27
+ vi.spyOn(console, 'warn').mockImplementation(() => { });
28
+ });
29
+ afterEach(() => vi.restoreAllMocks());
30
+ describe('config secret masking (BLOCKER-1)', () => {
31
+ it('masks coordination-service-token on set', () => {
32
+ runConfig({ subcommand: 'set', key: 'coordination-service-token', value: SECRET });
33
+ const out = logs.join('\n');
34
+ expect(out).toContain('***');
35
+ expect(out).not.toContain(SECRET);
36
+ });
37
+ it('masks coordination-service-token on get', () => {
38
+ store.data['coordination-service-token'] = SECRET;
39
+ runConfig({ subcommand: 'get', key: 'coordination-service-token' });
40
+ const out = logs.join('\n');
41
+ expect(out).toContain('***');
42
+ expect(out).not.toContain(SECRET);
43
+ });
44
+ it('masks coordination-service-token on list', () => {
45
+ store.data['coordination-service-token'] = SECRET;
46
+ store.data['coordination-service-url'] = 'https://coord.example:8443';
47
+ runConfig({ subcommand: 'list' });
48
+ const out = logs.join('\n');
49
+ expect(out).not.toContain(SECRET);
50
+ expect(out).toContain('***');
51
+ // Non-secret url is shown in the clear.
52
+ expect(out).toContain('https://coord.example:8443');
53
+ });
54
+ });
55
+ //# sourceMappingURL=config-command.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-command.test.js","sourceRoot":"","sources":["../../src/__tests__/config-command.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * coordination brief (FR-5) + watch (FR-6 poll) -- consumption layer.
3
+ *
4
+ * brief drains up to N, renders the fenced block, confirms only after a
5
+ * successful render, and stays under the token ceiling. watch polls P0/P1 and
6
+ * confirms delivered batches.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=coordination-brief.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordination-brief.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/coordination-brief.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * coordination brief (FR-5) + watch (FR-6 poll) -- consumption layer.
3
+ *
4
+ * brief drains up to N, renders the fenced block, confirms only after a
5
+ * successful render, and stays under the token ceiling. watch polls P0/P1 and
6
+ * confirms delivered batches.
7
+ */
8
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
9
+ import { runBrief, runWatch } from '../commands/coordination.js';
10
+ let logs;
11
+ beforeEach(() => {
12
+ logs = [];
13
+ vi.spyOn(console, 'log').mockImplementation((m) => { logs.push(String(m)); });
14
+ });
15
+ afterEach(() => vi.restoreAllMocks());
16
+ function stubClient(over = {}) {
17
+ return {
18
+ async enqueue() { return { messageId: 'm1', duplicate: false }; },
19
+ async drain() { return { messages: [], remaining: 0, batchId: '' }; },
20
+ async confirmDrain() { },
21
+ async ack() { },
22
+ ...over,
23
+ };
24
+ }
25
+ function msgs(n, bodyLen = 40) {
26
+ return Array.from({ length: n }, (_, i) => ({ id: `m${i}`, from: 'retrieval', to: 'mcp', priority: 'p2', body: 'x'.repeat(bodyLen), timestamp: 't' }));
27
+ }
28
+ describe('runBrief (FR-5)', () => {
29
+ it('prints the empty line and does not confirm when nothing is pending', async () => {
30
+ const confirmed = [];
31
+ const client = stubClient({ async confirmDrain(id) { confirmed.push(id); } });
32
+ await runBrief({}, { client });
33
+ expect(logs.join('\n')).toContain('no cross-team coordination pending');
34
+ expect(confirmed).toEqual([]);
35
+ });
36
+ it('renders the fenced brief, shows "M more pending", and confirms after render', async () => {
37
+ const confirmed = [];
38
+ const client = stubClient({
39
+ async drain() { return { messages: msgs(10), remaining: 40, batchId: 'b1' }; },
40
+ async confirmDrain(id) { confirmed.push(id); },
41
+ });
42
+ await runBrief({}, { client });
43
+ const out = logs.join('\n');
44
+ expect(out).toContain('MX_COORD_UNTRUSTED');
45
+ expect(out).toContain('40 more pending');
46
+ expect(confirmed).toEqual(['b1']);
47
+ });
48
+ it('stays under the token ceiling (<8000 chars) for a 50-message backlog', async () => {
49
+ // The brief drains its own limit (10 by default); bodies are truncated.
50
+ const client = stubClient({
51
+ async drain(opts) {
52
+ const limit = opts.limit ?? 10;
53
+ return { messages: msgs(50, 2000).slice(0, limit), remaining: 50 - limit, batchId: 'b1' };
54
+ },
55
+ });
56
+ await runBrief({}, { client });
57
+ const rendered = logs.join('\n');
58
+ expect(rendered.length).toBeLessThan(8000);
59
+ });
60
+ it('does NOT confirm when the render fails mid-way (batch stays pending)', async () => {
61
+ const confirmed = [];
62
+ const client = stubClient({
63
+ async drain() { return { messages: msgs(2), remaining: 0, batchId: 'b1' }; },
64
+ async confirmDrain(id) { confirmed.push(id); },
65
+ });
66
+ console.log.mockImplementation(() => { throw new Error('render failed'); });
67
+ await expect(runBrief({}, { client })).rejects.toThrow('render failed');
68
+ expect(confirmed).toEqual([]);
69
+ });
70
+ });
71
+ describe('runWatch (FR-6 poll)', () => {
72
+ it('polls P0/P1 once, prints a fenced render, and confirms the batch', async () => {
73
+ const drainCalls = [];
74
+ const confirmed = [];
75
+ const client = stubClient({
76
+ async drain(opts) {
77
+ drainCalls.push(opts);
78
+ return { messages: [{ id: 'm1', from: 'retrieval', to: 'mcp', priority: 'p0', body: 'urgent', timestamp: 't' }], remaining: 0, batchId: 'b1' };
79
+ },
80
+ async confirmDrain(id) { confirmed.push(id); },
81
+ });
82
+ await runWatch({ once: true }, { client, sleep: async () => { } });
83
+ expect(drainCalls[0].priorities).toEqual(['p0', 'p1']); // P2/P3 left for the brief
84
+ expect(logs.join('\n')).toContain('MX_COORD_UNTRUSTED');
85
+ expect(confirmed).toEqual(['b1']);
86
+ });
87
+ it('prints nothing and does not confirm when no P0/P1 is pending', async () => {
88
+ const confirmed = [];
89
+ const client = stubClient({ async confirmDrain(id) { confirmed.push(id); } });
90
+ await runWatch({ once: true }, { client, sleep: async () => { } });
91
+ expect(logs).toEqual([]);
92
+ expect(confirmed).toEqual([]);
93
+ });
94
+ });
95
+ //# sourceMappingURL=coordination-brief.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordination-brief.test.js","sourceRoot":"","sources":["../../src/__tests__/coordination-brief.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * coordination-client -- HTTP client for the coordination service (FR-7).
3
+ *
4
+ * Uses a stubbed global fetch to assert the client builds correct requests and
5
+ * maps transport failures to ServiceUnreachableError and non-2xx to
6
+ * ServiceHttpError.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=coordination-client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordination-client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/coordination-client.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * coordination-client -- HTTP client for the coordination service (FR-7).
3
+ *
4
+ * Uses a stubbed global fetch to assert the client builds correct requests and
5
+ * maps transport failures to ServiceUnreachableError and non-2xx to
6
+ * ServiceHttpError.
7
+ */
8
+ import { describe, it, expect, vi, afterEach } from 'vitest';
9
+ import { HttpCoordinationClient, ServiceUnreachableError, resolveClientConfig, } from '../lib/coordination-client.js';
10
+ const cfg = { url: 'https://coord.example:8443', token: 'tok-abc' };
11
+ function stubFetch(impl) {
12
+ vi.stubGlobal('fetch', vi.fn((url, init) => Promise.resolve(impl(url, init))));
13
+ }
14
+ afterEach(() => {
15
+ vi.unstubAllGlobals();
16
+ delete process.env.MX_COORD_SERVICE_URL;
17
+ delete process.env.MX_COORD_SERVICE_TOKEN;
18
+ });
19
+ describe('coordination-client', () => {
20
+ it('enqueue posts to /enqueue with bearer auth and JSON body', async () => {
21
+ let seenUrl = '';
22
+ let seenInit = {};
23
+ stubFetch((url, init) => {
24
+ seenUrl = url;
25
+ seenInit = init;
26
+ return new Response(JSON.stringify({ messageId: 'm1', duplicate: false }), { status: 200 });
27
+ });
28
+ const client = new HttpCoordinationClient(cfg);
29
+ const res = await client.enqueue({ to: 'platform', priority: 'p1', body: 'hi' });
30
+ expect(res.messageId).toBe('m1');
31
+ expect(seenUrl).toBe('https://coord.example:8443/enqueue');
32
+ expect(seenInit.headers.authorization).toBe('Bearer tok-abc');
33
+ expect(JSON.parse(seenInit.body)).toMatchObject({ to: 'platform', priority: 'p1', body: 'hi' });
34
+ });
35
+ it('drain returns messages + batchId', async () => {
36
+ stubFetch(() => new Response(JSON.stringify({ messages: [{ id: 'm1', from: 'mcp', to: 'platform', priority: 'p1', body: 'x', timestamp: 't' }], remaining: 0, batchId: 'batch_1' }), { status: 200 }));
37
+ const client = new HttpCoordinationClient(cfg);
38
+ const res = await client.drain({ limit: 5 });
39
+ expect(res.messages).toHaveLength(1);
40
+ expect(res.batchId).toBe('batch_1');
41
+ });
42
+ it('drain passes a priority filter through to the service', async () => {
43
+ let seenBody;
44
+ stubFetch((_url, init) => {
45
+ seenBody = JSON.parse(init.body);
46
+ return new Response(JSON.stringify({ messages: [], remaining: 0, batchId: '' }), { status: 200 });
47
+ });
48
+ const client = new HttpCoordinationClient(cfg);
49
+ await client.drain({ limit: 5, priorities: ['p0', 'p1'] });
50
+ expect(seenBody).toEqual({ limit: 5, priorities: ['p0', 'p1'] });
51
+ });
52
+ it('confirmDrain posts the batchId to /drain/confirm', async () => {
53
+ let seenUrl = '';
54
+ let seenBody;
55
+ stubFetch((url, init) => {
56
+ seenUrl = url;
57
+ seenBody = JSON.parse(init.body);
58
+ return new Response(JSON.stringify({ confirmed: true }), { status: 200 });
59
+ });
60
+ const client = new HttpCoordinationClient(cfg);
61
+ await client.confirmDrain('batch_9');
62
+ expect(seenUrl).toBe('https://coord.example:8443/drain/confirm');
63
+ expect(seenBody).toEqual({ batchId: 'batch_9' });
64
+ });
65
+ it('maps a network failure to ServiceUnreachableError', async () => {
66
+ vi.stubGlobal('fetch', vi.fn(() => Promise.reject(new Error('ECONNREFUSED'))));
67
+ const client = new HttpCoordinationClient(cfg);
68
+ await expect(client.enqueue({ to: 'platform', priority: 'p2', body: 'x' })).rejects.toBeInstanceOf(ServiceUnreachableError);
69
+ });
70
+ it('maps a non-2xx response to ServiceHttpError with the service error message', async () => {
71
+ stubFetch(() => new Response(JSON.stringify({ error: 'rate limit exceeded' }), { status: 429 }));
72
+ const client = new HttpCoordinationClient(cfg);
73
+ await expect(client.enqueue({ to: 'platform', priority: 'p0', body: 'x' })).rejects.toMatchObject({ name: 'ServiceHttpError', status: 429 });
74
+ });
75
+ it('resolveClientConfig reads URL + token from the environment and strips a trailing slash', () => {
76
+ process.env.MX_COORD_SERVICE_URL = 'https://coord.example:8443/';
77
+ process.env.MX_COORD_SERVICE_TOKEN = 'env-tok';
78
+ const resolved = resolveClientConfig();
79
+ expect(resolved).toEqual({ url: 'https://coord.example:8443', token: 'env-tok' });
80
+ });
81
+ });
82
+ //# sourceMappingURL=coordination-client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordination-client.test.js","sourceRoot":"","sources":["../../src/__tests__/coordination-client.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * coordination commands -- FR-7 escalate/drain + FR-8 degraded mode.
3
+ *
4
+ * runEscalate writes to the local fallback when the service is unreachable
5
+ * (non-lossy, exit 0), sends normally when reachable, and fails cleanly on a
6
+ * symlinked fallback path. runDrain confirms the batch only after a full render.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=coordination-command.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordination-command.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/coordination-command.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * coordination commands -- FR-7 escalate/drain + FR-8 degraded mode.
3
+ *
4
+ * runEscalate writes to the local fallback when the service is unreachable
5
+ * (non-lossy, exit 0), sends normally when reachable, and fails cleanly on a
6
+ * symlinked fallback path. runDrain confirms the batch only after a full render.
7
+ */
8
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
9
+ import { mkdtempSync, rmSync, symlinkSync, writeFileSync, readFileSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { tmpdir } from 'os';
12
+ import { runEscalate, runDrain } from '../commands/coordination.js';
13
+ import { readFallbackEntries } from '../lib/coordination-fallback.js';
14
+ import { ServiceUnreachableError } from '../lib/coordination-client.js';
15
+ let dir;
16
+ let logs;
17
+ let errs;
18
+ beforeEach(() => {
19
+ dir = mkdtempSync(join(tmpdir(), 'coord-cmd-'));
20
+ process.env.CLAUDE_WORKTREE_PATH = dir;
21
+ process.env.CLAUDE_WORKTREE_NAME = 'mcp';
22
+ logs = [];
23
+ errs = [];
24
+ vi.spyOn(console, 'log').mockImplementation((m) => { logs.push(String(m)); });
25
+ vi.spyOn(console, 'error').mockImplementation((m) => { errs.push(String(m)); });
26
+ });
27
+ afterEach(() => {
28
+ vi.restoreAllMocks();
29
+ rmSync(dir, { recursive: true, force: true });
30
+ delete process.env.CLAUDE_WORKTREE_PATH;
31
+ delete process.env.CLAUDE_WORKTREE_NAME;
32
+ });
33
+ /** Build a client stub; override individual methods per test. */
34
+ function stubClient(over = {}) {
35
+ return {
36
+ async enqueue() { return { messageId: 'm1', duplicate: false }; },
37
+ async drain() { return { messages: [], remaining: 0, batchId: '' }; },
38
+ async confirmDrain() { },
39
+ async ack() { },
40
+ ...over,
41
+ };
42
+ }
43
+ const unreachableClient = stubClient({
44
+ async enqueue() { throw new ServiceUnreachableError(new Error('down')); },
45
+ async drain() { throw new ServiceUnreachableError(new Error('down')); },
46
+ });
47
+ describe('runEscalate (FR-7 + FR-8)', () => {
48
+ it('degrades to the local fallback when the service is unreachable, non-lossy', async () => {
49
+ await runEscalate({ to: 'platform', priority: 'p1', body: 'help' }, { client: unreachableClient });
50
+ expect(logs.join('\n')).toContain('queued locally, will flush on reconnect');
51
+ const entries = readFallbackEntries(join(dir, '.mx-coordination-fallback.jsonl'));
52
+ expect(entries).toHaveLength(1);
53
+ expect(entries[0]).toMatchObject({ team: 'mcp', to: 'platform', priority: 'p1', body: 'help' });
54
+ });
55
+ it('sends normally when the service is reachable', async () => {
56
+ const sent = [];
57
+ const okClient = stubClient({ async enqueue(req) { sent.push(req.body); return { messageId: 'm1', duplicate: false }; } });
58
+ await runEscalate({ to: 'platform', priority: 'p2', body: 'live' }, { client: okClient });
59
+ expect(logs.join('\n')).toContain('escalated: m1');
60
+ expect(sent).toContain('live');
61
+ });
62
+ it('fails CLEANLY (no stack, non-zero exit) when the fallback path is a symlink (MINOR-6)', async () => {
63
+ const fp = join(dir, '.mx-coordination-fallback.jsonl');
64
+ const target = join(dir, 'evil');
65
+ writeFileSync(target, '');
66
+ symlinkSync(target, fp);
67
+ const exit = vi.spyOn(process, 'exit').mockImplementation((() => { throw new Error('exit'); }));
68
+ const errWrites = [];
69
+ vi.spyOn(process.stderr, 'write').mockImplementation(((c) => { errWrites.push(String(c)); return true; }));
70
+ await expect(runEscalate({ to: 'platform', priority: 'p1', body: 'x' }, { client: unreachableClient })).rejects.toThrow('exit');
71
+ expect(exit).toHaveBeenCalledWith(1);
72
+ expect(errWrites.join('')).toMatch(/refusing to write; escalation NOT queued/);
73
+ expect(readFileSync(target, 'utf8')).toBe(''); // symlink target untouched
74
+ });
75
+ });
76
+ describe('runDrain (FR-7 + confirm)', () => {
77
+ it('prints the empty-inbox line when nothing is pending', async () => {
78
+ await runDrain({}, { client: stubClient() });
79
+ expect(logs.join('\n')).toContain('no cross-team coordination pending');
80
+ });
81
+ it('prints messages, a "more pending" hint, and confirms the batch after render (MAJOR-2)', async () => {
82
+ const confirmed = [];
83
+ const drainRes = { messages: [{ id: 'm1', from: 'retrieval', to: 'mcp', priority: 'p0', body: 'urgent', timestamp: 't' }], remaining: 4, batchId: 'b1' };
84
+ const client = stubClient({ async drain() { return drainRes; }, async confirmDrain(id) { confirmed.push(id); } });
85
+ await runDrain({ limit: '1' }, { client });
86
+ const out = logs.join('\n');
87
+ expect(out).toContain('[p0] m1 from retrieval: urgent');
88
+ expect(out).toContain('4 more pending');
89
+ expect(confirmed).toEqual(['b1']); // confirmed with the returned batchId
90
+ });
91
+ it('does NOT confirm when rendering fails mid-way (batch stays pending — safety net)', async () => {
92
+ const confirmed = [];
93
+ const drainRes = { messages: [{ id: 'm1', from: 'x', to: 'mcp', priority: 'p1', body: 'b', timestamp: 't' }], remaining: 0, batchId: 'b1' };
94
+ const client = stubClient({ async drain() { return drainRes; }, async confirmDrain(id) { confirmed.push(id); } });
95
+ // Make the render throw.
96
+ console.log.mockImplementation(() => { throw new Error('render failed'); });
97
+ await expect(runDrain({}, { client })).rejects.toThrow('render failed');
98
+ expect(confirmed).toEqual([]); // never confirmed
99
+ });
100
+ it('does NOT confirm when --no-confirm is set', async () => {
101
+ const confirmed = [];
102
+ const drainRes = { messages: [{ id: 'm1', from: 'x', to: 'mcp', priority: 'p2', body: 'b', timestamp: 't' }], remaining: 0, batchId: 'b1' };
103
+ const client = stubClient({ async drain() { return drainRes; }, async confirmDrain(id) { confirmed.push(id); } });
104
+ await runDrain({ noConfirm: true }, { client });
105
+ expect(confirmed).toEqual([]);
106
+ });
107
+ });
108
+ //# sourceMappingURL=coordination-command.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordination-command.test.js","sourceRoot":"","sources":["../../src/__tests__/coordination-command.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * coordination-fallback -- FR-8 degraded-mode durable fallback.
3
+ *
4
+ * Covers: outage write is durable, a pre-planted symlink is rejected (O_NOFOLLOW),
5
+ * flush is idempotent and clears the file, an interrupted flush (service still
6
+ * down mid-batch) keeps remaining entries, and a stale lease is re-acquired.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=coordination-fallback.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordination-fallback.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/coordination-fallback.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}