nubos-pilot 1.0.2 → 1.0.4

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.
@@ -0,0 +1,241 @@
1
+ 'use strict';
2
+
3
+ const { test } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const { Writable } = require('node:stream');
9
+
10
+ const memoryIndex = require('./memory-index.cjs');
11
+ const memoryQuery = require('./memory-query.cjs');
12
+ const memoryAdd = require('./memory-add.cjs');
13
+ const memoryRebuild = require('./memory-rebuild.cjs');
14
+ const memoryStats = require('./memory-stats.cjs');
15
+
16
+ function _sandbox(enabled = true) {
17
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-memory-cli-'));
18
+ fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
19
+ if (enabled) {
20
+ fs.writeFileSync(
21
+ path.join(root, '.nubos-pilot', 'config.json'),
22
+ JSON.stringify({ memory: { enabled: true, model: 'mock-v1', alpha: 0.6 } }),
23
+ 'utf-8',
24
+ );
25
+ } else {
26
+ fs.writeFileSync(
27
+ path.join(root, '.nubos-pilot', 'config.json'),
28
+ JSON.stringify({ memory: { enabled: false } }),
29
+ 'utf-8',
30
+ );
31
+ }
32
+ return root;
33
+ }
34
+
35
+ function _capture() {
36
+ const chunks = [];
37
+ const stream = new Writable({
38
+ write(chunk, _enc, cb) { chunks.push(chunk); cb(); },
39
+ });
40
+ stream.text = () => Buffer.concat(chunks).toString('utf-8');
41
+ return stream;
42
+ }
43
+
44
+ function _hashEmbed(text, dim) {
45
+ const v = new Float32Array(dim);
46
+ let s = 1;
47
+ for (let i = 0; i < text.length; i++) s = (s * 31 + text.charCodeAt(i)) & 0x7fffffff;
48
+ for (let i = 0; i < dim; i++) {
49
+ s = (s * 1103515245 + 12345) & 0x7fffffff;
50
+ v[i] = (s % 1000) / 1000;
51
+ }
52
+ let n = 0;
53
+ for (let i = 0; i < dim; i++) n += v[i] * v[i];
54
+ n = Math.sqrt(n) || 1;
55
+ for (let i = 0; i < dim; i++) v[i] /= n;
56
+ return v;
57
+ }
58
+
59
+ function _mockProvider() {
60
+ return {
61
+ modelId: 'mock-v1',
62
+ dim: 8,
63
+ async embed(texts) { return texts.map((t) => _hashEmbed(t, 8)); },
64
+ };
65
+ }
66
+
67
+ function _mockIndex() {
68
+ let entries = new Map();
69
+ return {
70
+ add(id, vec) { entries.set(id, vec); },
71
+ remove(id) { entries.delete(id); },
72
+ size() { return entries.size; },
73
+ isEmpty() { return entries.size === 0; },
74
+ clear() { entries = new Map(); },
75
+ search(vec, k) {
76
+ const hits = [];
77
+ for (const [id, v] of entries.entries()) {
78
+ let dot = 0;
79
+ for (let i = 0; i < vec.length; i++) dot += vec[i] * v[i];
80
+ hits.push({ id, score: dot });
81
+ }
82
+ hits.sort((a, b) => b.score - a.score);
83
+ return hits.slice(0, k);
84
+ },
85
+ };
86
+ }
87
+
88
+ function _mocks(root) {
89
+ return { cwd: root, provider: _mockProvider(), indexEngine: _mockIndex(), alpha: 0.6 };
90
+ }
91
+
92
+ test('MEMC-1: memory-index --records JSON array writes records and prints {added, skipped}', async () => {
93
+ const root = _sandbox();
94
+ try {
95
+ const out = _capture();
96
+ const records = JSON.stringify([
97
+ { type: 'learning', title: 'A', body: 'b1' },
98
+ { type: 'learning', title: 'B', body: 'b2' },
99
+ ]);
100
+ const exit = await memoryIndex.run(['--records', records], { ..._mocks(root), stdout: out });
101
+ assert.equal(exit, 0);
102
+ const parsed = JSON.parse(out.text());
103
+ assert.equal(parsed.added, 2);
104
+ assert.equal(parsed.skipped, 0);
105
+ } finally {
106
+ fs.rmSync(root, { recursive: true, force: true });
107
+ }
108
+ });
109
+
110
+ test('MEMC-2: memory-index --records-file reads JSONL', async () => {
111
+ const root = _sandbox();
112
+ try {
113
+ const filePath = path.join(root, 'records.jsonl');
114
+ const lines = [
115
+ JSON.stringify({ type: 'critic', title: 'finding-1', body: 'b' }),
116
+ JSON.stringify({ type: 'research', title: 'decision-1', body: 'b' }),
117
+ ];
118
+ fs.writeFileSync(filePath, lines.join('\n'), 'utf-8');
119
+
120
+ const out = _capture();
121
+ await memoryIndex.run(['--records-file', filePath], { ..._mocks(root), stdout: out });
122
+ const parsed = JSON.parse(out.text());
123
+ assert.equal(parsed.added, 2);
124
+ } finally {
125
+ fs.rmSync(root, { recursive: true, force: true });
126
+ }
127
+ });
128
+
129
+ test('MEMC-3: memory-query prints top-k JSON array', async () => {
130
+ const root = _sandbox();
131
+ try {
132
+ const ctx = _mocks(root);
133
+ await memoryIndex.run(
134
+ ['--records', JSON.stringify([
135
+ { type: 'learning', title: 'A', body: 'a' },
136
+ { type: 'learning', title: 'B', body: 'b' },
137
+ ])],
138
+ { ...ctx, stdout: _capture() },
139
+ );
140
+
141
+ const queryOut = _capture();
142
+ await memoryQuery.run(['--text', 'a', '--k', '5'], { ...ctx, stdout: queryOut });
143
+ const hits = JSON.parse(queryOut.text());
144
+ assert.ok(Array.isArray(hits));
145
+ assert.ok(hits.length >= 1);
146
+ assert.ok('id' in hits[0] && 'score' in hits[0] && 'record' in hits[0]);
147
+ } finally {
148
+ fs.rmSync(root, { recursive: true, force: true });
149
+ }
150
+ });
151
+
152
+ test('MEMC-4: memory-add inserts a single record and prints {added, id}', async () => {
153
+ const root = _sandbox();
154
+ try {
155
+ const out = _capture();
156
+ const exit = await memoryAdd.run(
157
+ ['--type', 'learning', '--title', 'A', '--body', 'b', '--tags', 'feature-flags,filament'],
158
+ { ..._mocks(root), stdout: out },
159
+ );
160
+ assert.equal(exit, 0);
161
+ const parsed = JSON.parse(out.text());
162
+ assert.equal(parsed.added, true);
163
+ assert.match(parsed.id, /^[0-9a-f]{8}-/);
164
+ } finally {
165
+ fs.rmSync(root, { recursive: true, force: true });
166
+ }
167
+ });
168
+
169
+ test('MEMC-5: memory-stats returns count + dim + model', async () => {
170
+ const root = _sandbox();
171
+ try {
172
+ const ctx = _mocks(root);
173
+ await memoryAdd.run(['--type', 'learning', '--title', 'A', '--body', 'b'], { ...ctx, stdout: _capture() });
174
+
175
+ const statsOut = _capture();
176
+ memoryStats.run([], { ...ctx, stdout: statsOut });
177
+ const stats = JSON.parse(statsOut.text());
178
+ assert.equal(stats.count, 1);
179
+ assert.equal(stats.dim, 8);
180
+ assert.equal(stats.model, 'mock-v1');
181
+ } finally {
182
+ fs.rmSync(root, { recursive: true, force: true });
183
+ }
184
+ });
185
+
186
+ test('MEMC-6: memory-rebuild re-embeds existing records', async () => {
187
+ const root = _sandbox();
188
+ try {
189
+ const ctx = _mocks(root);
190
+ await memoryIndex.run(
191
+ ['--records', JSON.stringify([
192
+ { type: 'learning', title: 'A', body: 'a' },
193
+ { type: 'learning', title: 'B', body: 'b' },
194
+ ])],
195
+ { ...ctx, stdout: _capture() },
196
+ );
197
+
198
+ const rebuildOut = _capture();
199
+ await memoryRebuild.run([], { ...ctx, stdout: rebuildOut });
200
+ const result = JSON.parse(rebuildOut.text());
201
+ assert.equal(result.reembedded, 2);
202
+ } finally {
203
+ fs.rmSync(root, { recursive: true, force: true });
204
+ }
205
+ });
206
+
207
+ test('MEMC-7: production path with memory.enabled=false errors with memory-disabled', () => {
208
+ const root = _sandbox(false);
209
+ try {
210
+ assert.throws(
211
+ () => memoryStats.run([], { cwd: root, stdout: _capture() }),
212
+ (err) => err.name === 'NubosPilotError' && err.code === 'memory-disabled',
213
+ );
214
+ } finally {
215
+ fs.rmSync(root, { recursive: true, force: true });
216
+ }
217
+ });
218
+
219
+ test('MEMC-8: memory-query without text errors with memory-query-missing-text', async () => {
220
+ const root = _sandbox();
221
+ try {
222
+ await assert.rejects(
223
+ memoryQuery.run([], { ..._mocks(root), stdout: _capture() }),
224
+ (err) => err.code === 'memory-query-missing-text',
225
+ );
226
+ } finally {
227
+ fs.rmSync(root, { recursive: true, force: true });
228
+ }
229
+ });
230
+
231
+ test('MEMC-9: memory-index without records errors with memory-index-missing-records', async () => {
232
+ const root = _sandbox();
233
+ try {
234
+ await assert.rejects(
235
+ memoryIndex.run([], { ..._mocks(root), stdout: _capture() }),
236
+ (err) => err.code === 'memory-index-missing-records',
237
+ );
238
+ } finally {
239
+ fs.rmSync(root, { recursive: true, force: true });
240
+ }
241
+ });
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ const { NubosPilotError } = require('../../lib/core.cjs');
4
+ const { archive } = require('../../lib/messaging.cjs');
5
+
6
+ function _parseArgs(args) {
7
+ const out = { id: null };
8
+ for (let i = 0; i < args.length; i++) {
9
+ const a = args[i];
10
+ if (a === '--id') { out.id = args[++i] || null; continue; }
11
+ if (!a.startsWith('--') && !out.id) { out.id = a; continue; }
12
+ }
13
+ return out;
14
+ }
15
+
16
+ function run(args, opts) {
17
+ const o = opts || {};
18
+ const cwd = o.cwd || process.cwd();
19
+ const stdout = o.stdout || process.stdout;
20
+ const parsed = _parseArgs(Array.isArray(args) ? args : []);
21
+
22
+ if (!parsed.id) {
23
+ throw new NubosPilotError(
24
+ 'messages-missing-id',
25
+ 'msg-id required (positional or --id)',
26
+ {},
27
+ );
28
+ }
29
+
30
+ archive(parsed.id, cwd);
31
+ stdout.write(JSON.stringify({ archived: parsed.id }) + '\n');
32
+ return 0;
33
+ }
34
+
35
+ module.exports = { run, _parseArgs };
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const { inbox } = require('../../lib/messaging.cjs');
4
+
5
+ function _parseArgs(args) {
6
+ const out = { agent: null, kind: null, since: null, phase: null };
7
+ for (let i = 0; i < args.length; i++) {
8
+ const a = args[i];
9
+ if (a === '--agent') { out.agent = args[++i] || null; continue; }
10
+ if (a === '--kind') { out.kind = args[++i] || null; continue; }
11
+ if (a === '--since') { out.since = args[++i] || null; continue; }
12
+ if (a === '--phase') { out.phase = args[++i] || null; continue; }
13
+ if (a === '--task') { out.phase = args[++i] || null; continue; }
14
+ }
15
+ return out;
16
+ }
17
+
18
+ function run(args, opts) {
19
+ const o = opts || {};
20
+ const cwd = o.cwd || process.cwd();
21
+ const stdout = o.stdout || process.stdout;
22
+ const parsed = _parseArgs(Array.isArray(args) ? args : []);
23
+
24
+ const result = inbox(parsed.agent, {
25
+ kind: parsed.kind || undefined,
26
+ since: parsed.since || undefined,
27
+ phase: parsed.phase || undefined,
28
+ }, cwd);
29
+
30
+ stdout.write(JSON.stringify(result) + '\n');
31
+ return 0;
32
+ }
33
+
34
+ module.exports = { run, _parseArgs };
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const { NubosPilotError } = require('../../lib/core.cjs');
5
+ const { send } = require('../../lib/messaging.cjs');
6
+
7
+ function _parseArgs(args) {
8
+ const out = {
9
+ from: null, to: null, phase: null, round: null,
10
+ kind: null, subject: null, body: null, bodyFile: null,
11
+ expectsReply: false, inReplyTo: null,
12
+ };
13
+ for (let i = 0; i < args.length; i++) {
14
+ const a = args[i];
15
+ if (a === '--from') { out.from = args[++i] || null; continue; }
16
+ if (a === '--to') { out.to = args[++i] || null; continue; }
17
+ if (a === '--phase') { out.phase = args[++i] || null; continue; }
18
+ if (a === '--task') { out.phase = args[++i] || null; continue; }
19
+ if (a === '--round') { out.round = args[++i] || null; continue; }
20
+ if (a === '--kind') { out.kind = args[++i] || null; continue; }
21
+ if (a === '--subject') { out.subject = args[++i] || null; continue; }
22
+ if (a === '--body') { out.body = args[++i] || null; continue; }
23
+ if (a === '--body-file') { out.bodyFile = args[++i] || null; continue; }
24
+ if (a === '--expects-reply') { out.expectsReply = true; continue; }
25
+ if (a === '--in-reply-to') { out.inReplyTo = args[++i] || null; continue; }
26
+ }
27
+ return out;
28
+ }
29
+
30
+ function run(args, opts) {
31
+ const o = opts || {};
32
+ const cwd = o.cwd || process.cwd();
33
+ const stdout = o.stdout || process.stdout;
34
+ const parsed = _parseArgs(Array.isArray(args) ? args : []);
35
+
36
+ let body = parsed.body || '';
37
+ if (parsed.bodyFile) {
38
+ try { body = fs.readFileSync(parsed.bodyFile, 'utf-8'); }
39
+ catch (err) {
40
+ throw new NubosPilotError(
41
+ 'messages-body-file-read-failed',
42
+ 'failed to read --body-file: ' + (err && err.message),
43
+ { path: parsed.bodyFile },
44
+ );
45
+ }
46
+ }
47
+
48
+ let round = null;
49
+ if (parsed.round !== null && parsed.round !== '') {
50
+ const n = Number(parsed.round);
51
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0) {
52
+ throw new NubosPilotError(
53
+ 'messages-invalid-round',
54
+ '--round must be a non-negative integer; got: ' + JSON.stringify(parsed.round),
55
+ { round: parsed.round },
56
+ );
57
+ }
58
+ round = n;
59
+ }
60
+
61
+ const result = send({
62
+ from: parsed.from,
63
+ to: parsed.to,
64
+ phase: parsed.phase,
65
+ round,
66
+ kind: parsed.kind,
67
+ subject: parsed.subject,
68
+ body,
69
+ expects_reply: parsed.expectsReply,
70
+ in_reply_to: parsed.inReplyTo,
71
+ }, cwd);
72
+
73
+ stdout.write(JSON.stringify(result) + '\n');
74
+ return 0;
75
+ }
76
+
77
+ module.exports = { run, _parseArgs };
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ const { NubosPilotError } = require('../../lib/core.cjs');
4
+ const { thread } = require('../../lib/messaging.cjs');
5
+
6
+ function _parseArgs(args) {
7
+ const out = { id: null };
8
+ for (let i = 0; i < args.length; i++) {
9
+ const a = args[i];
10
+ if (a === '--id') { out.id = args[++i] || null; continue; }
11
+ if (!a.startsWith('--') && !out.id) { out.id = a; continue; }
12
+ }
13
+ return out;
14
+ }
15
+
16
+ function run(args, opts) {
17
+ const o = opts || {};
18
+ const cwd = o.cwd || process.cwd();
19
+ const stdout = o.stdout || process.stdout;
20
+ const parsed = _parseArgs(Array.isArray(args) ? args : []);
21
+
22
+ if (!parsed.id) {
23
+ throw new NubosPilotError(
24
+ 'messages-missing-id',
25
+ 'msg-id required (positional or --id)',
26
+ {},
27
+ );
28
+ }
29
+
30
+ const result = thread(parsed.id, cwd);
31
+ stdout.write(JSON.stringify(result) + '\n');
32
+ return 0;
33
+ }
34
+
35
+ module.exports = { run, _parseArgs };
@@ -0,0 +1,183 @@
1
+ 'use strict';
2
+
3
+ const { test } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const { Writable } = require('node:stream');
9
+
10
+ const send = require('./messages-send.cjs');
11
+ const inbox = require('./messages-inbox.cjs');
12
+ const archiveCmd = require('./messages-archive.cjs');
13
+ const thread = require('./messages-thread.cjs');
14
+
15
+ function _sandbox() {
16
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'np-messages-cli-'));
17
+ fs.mkdirSync(path.join(root, '.nubos-pilot'), { recursive: true });
18
+ return root;
19
+ }
20
+
21
+ function _capture() {
22
+ const chunks = [];
23
+ const stream = new Writable({
24
+ write(chunk, _enc, cb) { chunks.push(chunk); cb(); },
25
+ });
26
+ stream.text = () => Buffer.concat(chunks).toString('utf-8');
27
+ return stream;
28
+ }
29
+
30
+ test('MSC-1: messages-send writes inbox file and prints id JSON', () => {
31
+ const root = _sandbox();
32
+ try {
33
+ const out = _capture();
34
+ const exit = send.run(
35
+ ['--from', 'np-critic', '--to', 'np-executor',
36
+ '--phase', 'M016-S001-T0001', '--kind', 'notify',
37
+ '--subject', 'style', '--body', 'lint warning'],
38
+ { cwd: root, stdout: out },
39
+ );
40
+ assert.equal(exit, 0);
41
+ const parsed = JSON.parse(out.text());
42
+ assert.match(parsed.id, /^\d+-/);
43
+ assert.ok(fs.existsSync(parsed.path));
44
+ } finally {
45
+ fs.rmSync(root, { recursive: true, force: true });
46
+ }
47
+ });
48
+
49
+ test('MSC-2: messages-send --body-file reads body from disk', () => {
50
+ const root = _sandbox();
51
+ try {
52
+ const bodyFile = path.join(root, 'body.txt');
53
+ fs.writeFileSync(bodyFile, 'multi\nline\nbody', 'utf-8');
54
+ const out = _capture();
55
+ send.run(
56
+ ['--from', 'a', '--to', 'b', '--phase', 'P', '--kind', 'notify',
57
+ '--subject', 's', '--body-file', bodyFile],
58
+ { cwd: root, stdout: out },
59
+ );
60
+ const parsed = JSON.parse(out.text());
61
+ const stored = JSON.parse(fs.readFileSync(parsed.path, 'utf-8'));
62
+ assert.equal(stored.body, 'multi\nline\nbody');
63
+ } finally {
64
+ fs.rmSync(root, { recursive: true, force: true });
65
+ }
66
+ });
67
+
68
+ test('MSC-3: messages-send --expects-reply sets the flag', () => {
69
+ const root = _sandbox();
70
+ try {
71
+ const out = _capture();
72
+ send.run(
73
+ ['--from', 'a', '--to', 'b', '--phase', 'P', '--kind', 'request',
74
+ '--subject', 's', '--body', 'q', '--expects-reply'],
75
+ { cwd: root, stdout: out },
76
+ );
77
+ const parsed = JSON.parse(out.text());
78
+ const stored = JSON.parse(fs.readFileSync(parsed.path, 'utf-8'));
79
+ assert.equal(stored.expects_reply, true);
80
+ assert.equal(stored.kind, 'request');
81
+ } finally {
82
+ fs.rmSync(root, { recursive: true, force: true });
83
+ }
84
+ });
85
+
86
+ test('MSC-4: messages-inbox lists messages for an agent as JSON array', () => {
87
+ const root = _sandbox();
88
+ try {
89
+ const sendOut = _capture();
90
+ send.run(
91
+ ['--from', 'a', '--to', 'b', '--phase', 'P', '--kind', 'notify',
92
+ '--subject', 's', '--body', 'hi'],
93
+ { cwd: root, stdout: sendOut },
94
+ );
95
+ const inboxOut = _capture();
96
+ const exit = inbox.run(['--agent', 'b'], { cwd: root, stdout: inboxOut });
97
+ assert.equal(exit, 0);
98
+ const list = JSON.parse(inboxOut.text());
99
+ assert.ok(Array.isArray(list));
100
+ assert.equal(list.length, 1);
101
+ assert.equal(list[0].subject, 's');
102
+ } finally {
103
+ fs.rmSync(root, { recursive: true, force: true });
104
+ }
105
+ });
106
+
107
+ test('MSC-5: messages-archive moves a notify message and prints archived id', () => {
108
+ const root = _sandbox();
109
+ try {
110
+ const sendOut = _capture();
111
+ send.run(
112
+ ['--from', 'a', '--to', 'b', '--phase', 'P', '--kind', 'notify',
113
+ '--subject', 's', '--body', 'hi'],
114
+ { cwd: root, stdout: sendOut },
115
+ );
116
+ const sentId = JSON.parse(sendOut.text()).id;
117
+ const archiveOut = _capture();
118
+ const exit = archiveCmd.run([sentId], { cwd: root, stdout: archiveOut });
119
+ assert.equal(exit, 0);
120
+ assert.deepEqual(JSON.parse(archiveOut.text()), { archived: sentId });
121
+ } finally {
122
+ fs.rmSync(root, { recursive: true, force: true });
123
+ }
124
+ });
125
+
126
+ test('MSC-6: messages-archive without reply on expects_reply=true throws envelope code', () => {
127
+ const root = _sandbox();
128
+ try {
129
+ const sendOut = _capture();
130
+ send.run(
131
+ ['--from', 'a', '--to', 'b', '--phase', 'P', '--kind', 'request',
132
+ '--subject', 's', '--body', 'q', '--expects-reply'],
133
+ { cwd: root, stdout: sendOut },
134
+ );
135
+ const reqId = JSON.parse(sendOut.text()).id;
136
+ assert.throws(
137
+ () => archiveCmd.run([reqId], { cwd: root, stdout: _capture() }),
138
+ (err) => err.name === 'NubosPilotError' && err.code === 'messages-archive-without-reply',
139
+ );
140
+ } finally {
141
+ fs.rmSync(root, { recursive: true, force: true });
142
+ }
143
+ });
144
+
145
+ test('MSC-7: messages-thread prints causal reply chain', () => {
146
+ const root = _sandbox();
147
+ try {
148
+ const reqOut = _capture();
149
+ send.run(
150
+ ['--from', 'a', '--to', 'b', '--phase', 'P', '--kind', 'request',
151
+ '--subject', 's', '--body', 'q', '--expects-reply'],
152
+ { cwd: root, stdout: reqOut },
153
+ );
154
+ const reqId = JSON.parse(reqOut.text()).id;
155
+ const respOut = _capture();
156
+ send.run(
157
+ ['--from', 'b', '--to', 'a', '--phase', 'P', '--kind', 'response',
158
+ '--subject', 's', '--body', 'a', '--in-reply-to', reqId],
159
+ { cwd: root, stdout: respOut },
160
+ );
161
+
162
+ const threadOut = _capture();
163
+ thread.run([reqId], { cwd: root, stdout: threadOut });
164
+ const chain = JSON.parse(threadOut.text());
165
+ assert.equal(chain.length, 2);
166
+ assert.equal(chain[0].kind, 'request');
167
+ assert.equal(chain[1].kind, 'response');
168
+ } finally {
169
+ fs.rmSync(root, { recursive: true, force: true });
170
+ }
171
+ });
172
+
173
+ test('MSC-8: messages-archive without id throws messages-missing-id', () => {
174
+ const root = _sandbox();
175
+ try {
176
+ assert.throws(
177
+ () => archiveCmd.run([], { cwd: root, stdout: _capture() }),
178
+ (err) => err.name === 'NubosPilotError' && err.code === 'messages-missing-id',
179
+ );
180
+ } finally {
181
+ fs.rmSync(root, { recursive: true, force: true });
182
+ }
183
+ });