nubos-pilot 1.3.0 → 1.3.1

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 (38) hide show
  1. package/bin/np-tools/_commands.cjs +2 -0
  2. package/bin/np-tools/_elision-proxy-entry.cjs +13 -0
  3. package/bin/np-tools/elision-bench.cjs +67 -0
  4. package/bin/np-tools/elision-get.cjs +48 -0
  5. package/bin/np-tools/elision-get.test.cjs +66 -0
  6. package/bin/np-tools/loop-run-round.cjs +25 -11
  7. package/bin/np-tools/plan-milestone.cjs +1 -0
  8. package/bin/np-tools/research-phase.cjs +1 -1
  9. package/bin/np-tools/spawn-headless.cjs +62 -9
  10. package/lib/cache-align.cjs +78 -0
  11. package/lib/cache-align.test.cjs +69 -0
  12. package/lib/compress.cjs +495 -0
  13. package/lib/compress.test.cjs +267 -0
  14. package/lib/config-defaults.cjs +39 -0
  15. package/lib/config-schema.cjs +40 -4
  16. package/lib/elision-bench.cjs +409 -0
  17. package/lib/elision-bench.test.cjs +89 -0
  18. package/lib/elision-proxy.cjs +158 -0
  19. package/lib/elision-proxy.test.cjs +243 -0
  20. package/lib/elision.cjs +163 -0
  21. package/lib/elision.test.cjs +143 -0
  22. package/lib/nubosloop.cjs +1 -1
  23. package/lib/output-steering.cjs +68 -0
  24. package/lib/output-steering.test.cjs +74 -0
  25. package/lib/researcher-swarm.cjs +14 -3
  26. package/lib/runtime/agent-loop.cjs +36 -6
  27. package/lib/runtime/agent-loop.test.cjs +105 -0
  28. package/lib/runtime/dispatch.cjs +6 -6
  29. package/lib/runtime/dispatch.test.cjs +17 -3
  30. package/lib/runtime/providers/openai-compat.cjs +2 -1
  31. package/lib/runtime/providers/openai-compat.test.cjs +9 -0
  32. package/lib/runtime/tools/index.cjs +33 -1
  33. package/lib/runtime/tools/index.test.cjs +24 -0
  34. package/lib/schemas/data/elision-entry.v1.json +16 -0
  35. package/lib/token-cost.cjs +46 -0
  36. package/lib/token-cost.test.cjs +42 -0
  37. package/np-tools.cjs +2 -0
  38. package/package.json +1 -1
@@ -0,0 +1,267 @@
1
+ 'use strict';
2
+
3
+ const { test } = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+
6
+ const compress = require('./compress.cjs');
7
+
8
+ function bigJsonArray(n, withError) {
9
+ const arr = [];
10
+ for (let i = 0; i < n; i += 1) arr.push({ id: i, name: 'row-' + i, status: 'ok' });
11
+ if (withError) arr[Math.floor(n / 2)] = { id: 999, name: 'boom', status: 'ERROR: disk full' };
12
+ return JSON.stringify(arr);
13
+ }
14
+
15
+ test('CMP-1: detectType classifies json/diff/search/log/plain', () => {
16
+ assert.equal(compress.detectType(bigJsonArray(30)), 'json-array');
17
+ assert.equal(compress.detectType('--- a/x\n+++ b/x\n@@ -1 +1 @@\n-a\n+b\n'), 'diff');
18
+ const search = Array.from({ length: 8 }, (_, i) => 'src/a.js:' + (i + 1) + ':match').join('\n');
19
+ assert.equal(compress.detectType(search), 'search');
20
+ const log = Array.from({ length: 12 }, (_, i) => 'INFO line ' + i).concat('ERROR boom').join('\n');
21
+ assert.equal(compress.detectType(log), 'log');
22
+ assert.equal(compress.detectType('just some short prose'), 'plain');
23
+ });
24
+
25
+ test('CMP-2: crushJsonArray drops middle but keeps error item + head + tail', () => {
26
+ const res = compress.crushJsonArray(bigJsonArray(60, true));
27
+ assert.ok(res, 'should compress a 60-item array');
28
+ assert.ok(res.dropped > 0);
29
+ const kept = JSON.parse(res.compressed);
30
+ assert.ok(kept.length < 60);
31
+ assert.ok(kept.some((it) => /ERROR/.test(JSON.stringify(it))), 'error item survives');
32
+ assert.equal(kept[0].id, 0, 'head preserved');
33
+ });
34
+
35
+ test('CMP-3: adversarial — small array is not worth compressing (null)', () => {
36
+ assert.equal(compress.crushJsonArray(bigJsonArray(5)), null);
37
+ });
38
+
39
+ test('CMP-4: crushLog keeps ERROR + stack lines, drops INFO noise', () => {
40
+ const lines = [];
41
+ for (let i = 0; i < 200; i += 1) lines.push('INFO progress ' + i);
42
+ lines.push('ERROR something failed');
43
+ lines.push(' at foo (bar.js:1:1)');
44
+ const res = compress.crushLog(lines.join('\n'));
45
+ assert.ok(res && res.dropped > 0);
46
+ assert.match(res.compressed, /ERROR something failed/);
47
+ assert.match(res.compressed, /at foo/);
48
+ assert.match(res.compressed, /elided/);
49
+ });
50
+
51
+ test('CMP-5: crushSearch caps matches per file', () => {
52
+ const lines = [];
53
+ for (let i = 1; i <= 40; i += 1) lines.push('src/big.js:' + i + ':hit ' + i);
54
+ const res = compress.crushSearch(lines.join('\n'));
55
+ assert.ok(res && res.dropped > 0);
56
+ assert.ok(res.compressed.split('\n').length <= 30);
57
+ });
58
+
59
+ test('CMP-6: crushDiff keeps every +/- line', () => {
60
+ const diff = [
61
+ '--- a/x.js', '+++ b/x.js', '@@ -1,6 +1,6 @@',
62
+ ' ctx1', ' ctx2', ' ctx3', ' ctx4', '-removed', '+added', ' ctx5', ' ctx6', ' ctx7', ' ctx8',
63
+ ].join('\n');
64
+ const res = compress.crushDiff(diff);
65
+ assert.ok(res && res.dropped > 0);
66
+ assert.match(res.compressed, /-removed/);
67
+ assert.match(res.compressed, /\+added/);
68
+ });
69
+
70
+ test('CMP-7: compressBlock leaves small/plain text byte-identical', () => {
71
+ const small = 'hello world';
72
+ const out = compress.compressBlock(small, { minBlockBytes: 2048 });
73
+ assert.equal(out.changed, false);
74
+ assert.equal(out.compressed, small);
75
+ });
76
+
77
+ test('CMP-8: compressBlock appends a Elision marker when a store is provided', () => {
78
+ const seen = [];
79
+ const out = compress.compressBlock(bigJsonArray(80, true), {
80
+ minBlockBytes: 100,
81
+ store: (orig, type) => { seen.push(type); return 'abcdef012345'; },
82
+ });
83
+ assert.equal(out.changed, true);
84
+ assert.match(out.compressed, /⟦elided:abcdef012345 \d+ items elided · \d+ flagged kept · retrieve: nubos elision-get abcdef012345⟧/);
85
+ assert.deepEqual(seen, ['json-array']);
86
+ });
87
+
88
+ test('CMP-9: compressPrompt only touches fenced blocks and is deterministic', () => {
89
+ const blob = 'intro text\n\n```json\n' + bigJsonArray(80, true) + '\n```\n\noutro text';
90
+ const a = compress.compressPrompt(blob, { minBlockBytes: 100, store: () => 'aaaaaaaaaaaa' });
91
+ const b = compress.compressPrompt(blob, { minBlockBytes: 100, store: () => 'aaaaaaaaaaaa' });
92
+ assert.equal(a.stats.blocks_compressed, 1);
93
+ assert.ok(a.stats.bytes_after < a.stats.bytes_before);
94
+ assert.match(a.text, /^intro text/);
95
+ assert.match(a.text, /outro text$/);
96
+ assert.equal(a.text, b.text, 'deterministic');
97
+ });
98
+
99
+ test('CMP-10: compressPrompt with no large blocks leaves the blob unchanged', () => {
100
+ const blob = 'just a normal prompt with `tiny` inline code and no big fences.';
101
+ const out = compress.compressPrompt(blob, { minBlockBytes: 2048, store: () => 'x' });
102
+ assert.equal(out.stats.blocks_compressed, 0);
103
+ assert.equal(out.text, blob);
104
+ });
105
+
106
+ function gutter(text) {
107
+ return text.split('\n').map((l, i) => (i + 1) + '\t' + l).join('\n');
108
+ }
109
+
110
+ test('CMP-12: detectType sees through a line-number gutter (Read-tool style N\\t prefix)', () => {
111
+ const diff = '--- a/x\n+++ b/x\n@@ -1 +1 @@\n-a\n+b\n';
112
+ assert.equal(compress.detectType(gutter(diff)), 'diff');
113
+ assert.equal(compress.detectType(gutter(bigJsonArray(30))), 'json-array');
114
+ const search = Array.from({ length: 8 }, (_, i) => 'src/a.js:' + (i + 1) + ':match').join('\n');
115
+ assert.equal(compress.detectType(gutter(search)), 'search');
116
+ });
117
+
118
+ test('CMP-13: compressBlock crushes a gutter-prefixed diff and stores the raw original', () => {
119
+ const diffLines = ['--- a/x.js', '+++ b/x.js', '@@ -1,6 +1,6 @@'];
120
+ for (let i = 0; i < 60; i += 1) diffLines.push(' ctx unchanged line ' + i + ' ' + 'z'.repeat(40));
121
+ diffLines.push('-removed thing'); diffLines.push('+added thing');
122
+ const raw = gutter(diffLines.join('\n'));
123
+ let stored = null;
124
+ const out = compress.compressBlock(raw, {
125
+ minBlockBytes: 100,
126
+ store: (orig, type) => { stored = { orig, type }; return 'abcdef012345'; },
127
+ });
128
+ assert.equal(out.changed, true);
129
+ assert.equal(out.type, 'diff');
130
+ assert.ok(out.compressed.length < raw.length, 'gutter-prefixed diff now compresses');
131
+ assert.equal(stored.orig, raw, 'raw gutter-bearing original is stored byte-exact');
132
+ });
133
+
134
+ test('CMP-14: crushCode keeps signatures/closers + throw, elides statement bodies', () => {
135
+ const lines = ['class Svc {', ' run(input) {'];
136
+ for (let i = 0; i < 30; i += 1) lines.push(' const v' + i + ' = compute(input, ' + i + ');');
137
+ lines.push(' if (!input) throw new Error("missing input");');
138
+ for (let i = 0; i < 10; i += 1) lines.push(' this.acc.push({ k: ' + i + ', v: v' + i + ' });');
139
+ lines.push(' return this.acc;', ' }', '}');
140
+ const text = lines.join('\n');
141
+ const out = compress.crushCode(text);
142
+ assert.ok(out, 'a body-heavy class should crush');
143
+ assert.match(out.compressed, /class Svc \{/);
144
+ assert.match(out.compressed, /run\(input\) \{/);
145
+ assert.match(out.compressed, /throw new Error\("missing input"\)/);
146
+ assert.ok(!out.compressed.includes('const v15 ='), 'deep statement bodies are elided');
147
+ assert.ok(!out.compressed.includes('this.acc.push({ k: 5'), 'inline object-literal statements are elided');
148
+ assert.ok(out.compressed.length < text.length);
149
+ });
150
+
151
+ test('CMP-15: detectType reads source as "code" even with throw new Error (code before log)', () => {
152
+ const lines = ["'use strict';", "const x = require('./x.cjs');", 'function handler(req) {'];
153
+ for (let i = 0; i < 20; i += 1) lines.push(' const part' + i + ' = transform(req, ' + i + ');');
154
+ lines.push(' if (!req) throw new Error("bad request");', ' return part0;', '}');
155
+ assert.equal(compress.detectType(lines.join('\n')), 'code');
156
+ });
157
+
158
+ function bigProse(withCritical) {
159
+ const filler = 'The pipeline reads each source block and routes it to a type-specific reducer. '
160
+ + 'Ordinary statements are sampled while structural lines survive intact. '
161
+ + 'This keeps the crushed view legible without changing the stored original. ';
162
+ let text = filler.repeat(12);
163
+ if (withCritical) text += 'IMPORTANT: the retention window must never be shortened below the audit floor. ';
164
+ text += filler.repeat(12);
165
+ return text;
166
+ }
167
+
168
+ test('CMP-16: detectType classifies long prose as "prose", short prose stays "plain"', () => {
169
+ assert.equal(compress.detectType('just some short prose'), 'plain');
170
+ assert.equal(compress.detectType(bigProse(true)), 'prose');
171
+ });
172
+
173
+ test('CMP-17: crushProse keeps head + tail + critical sentence, samples the middle', () => {
174
+ const res = compress.crushProse(bigProse(true));
175
+ assert.ok(res && res.dropped > 0, 'a long prose block should crush');
176
+ assert.match(res.compressed, /IMPORTANT: the retention window must never be shortened/);
177
+ assert.match(res.compressed, /elided/);
178
+ assert.ok(res.compressed.length < bigProse(true).length);
179
+ });
180
+
181
+ test('CMP-18: brace counter is not fooled by braces inside strings/comments', () => {
182
+ const lines = ['function build(cfg) {'];
183
+ for (let i = 0; i < 20; i += 1) {
184
+ lines.push(' const tmpl' + i + ' = "open { and close } in a string"; // a } comment brace');
185
+ }
186
+ lines.push(' if (!cfg) throw new Error("no cfg");');
187
+ lines.push(' return tmpl0;', '}');
188
+ const text = lines.join('\n');
189
+ assert.equal(compress.detectType(text), 'code');
190
+ const out = compress.crushCode(text);
191
+ assert.ok(out && out.dropped > 0, 'string/comment braces must not block crushing');
192
+ assert.match(out.compressed, /function build\(cfg\) \{/);
193
+ assert.match(out.compressed, /throw new Error\("no cfg"\)/);
194
+ assert.ok(!out.compressed.includes('tmpl15'), 'interior statements are still elided');
195
+ });
196
+
197
+ test('CMP-19: Python is detected as code and crushed by indent, not braces', () => {
198
+ const lines = ['import os', '', 'class Worker:', ' def run(self, items):'];
199
+ for (let i = 0; i < 25; i += 1) lines.push(' value_' + i + ' = transform(items, ' + i + ')');
200
+ lines.push(' if not items:', ' raise ValueError("empty")');
201
+ lines.push(' return value_0');
202
+ const text = lines.join('\n');
203
+ assert.equal(compress.detectType(text), 'code');
204
+ const out = compress.crushCode(text);
205
+ assert.ok(out && out.dropped > 0, 'a body-heavy Python method should crush');
206
+ assert.match(out.compressed, /class Worker:/);
207
+ assert.match(out.compressed, /def run\(self, items\):/);
208
+ assert.match(out.compressed, /raise ValueError\("empty"\)/);
209
+ assert.match(out.compressed, /^import os/m);
210
+ assert.ok(!out.compressed.includes('value_15 ='), 'deep statement bodies are elided');
211
+ });
212
+
213
+ test('CMP-20: compressBlock routes prose through crushProse and stores the raw original', () => {
214
+ let stored = null;
215
+ const out = compress.compressBlock(bigProse(true), {
216
+ minBlockBytes: 100,
217
+ store: (orig, type) => { stored = { orig, type }; return 'beadfeed1234'; },
218
+ });
219
+ assert.equal(out.changed, true);
220
+ assert.equal(out.type, 'prose');
221
+ assert.equal(stored.orig, bigProse(true), 'raw prose original is stored byte-exact');
222
+ assert.match(out.compressed, /⟦elided:beadfeed1234 \d+ sentences elided/);
223
+ });
224
+
225
+ test('CMP-22: crushers emit a terse "what survived" summary used in the speaking marker', () => {
226
+ const logLines = [];
227
+ for (let i = 0; i < 200; i += 1) logLines.push('INFO progress ' + i);
228
+ logLines.push('ERROR boom', ' at f (a.js:1:1)');
229
+ assert.match(compress.crushLog(logLines.join('\n')).summary, /error\/stack line/);
230
+
231
+ const searchLines = [];
232
+ for (let i = 1; i <= 40; i += 1) searchLines.push('src/big.js:' + i + ':hit ' + i);
233
+ assert.match(compress.crushSearch(searchLines.join('\n')).summary, /\d+\/\d+ files/);
234
+
235
+ const code = ['class S {', ' run() {'];
236
+ for (let i = 0; i < 30; i += 1) code.push(' const v' + i + ' = f(' + i + ');');
237
+ code.push(' return v0;', ' }', '}');
238
+ assert.match(compress.crushCode(code.join('\n')).summary, /signature/);
239
+
240
+ const out = compress.compressBlock(bigJsonArray(80, true), { minBlockBytes: 100, store: () => 'abcdef012345' });
241
+ assert.match(out.compressed, /elided · .+ · retrieve:/, 'marker carries the gist between count and retrieve hint');
242
+ });
243
+
244
+ test('CMP-21: store opted-in but failing (null hash) declines compression — never a markerless lossy view', () => {
245
+ const out = compress.compressBlock(bigJsonArray(80, true), {
246
+ minBlockBytes: 100,
247
+ store: () => null,
248
+ });
249
+ assert.equal(out.changed, false, 'must not apply lossy compression without a recovery path');
250
+ assert.equal(out.compressed, bigJsonArray(80, true), 'original returned byte-identical');
251
+ assert.ok(!/elided/.test(out.compressed), 'no elision marker, no dropped content');
252
+ });
253
+
254
+ test('CMP-11: crushLogToBudget fits the byte budget and keeps errors', () => {
255
+ const lines = [];
256
+ for (let i = 0; i < 500; i += 1) lines.push('INFO noise line number ' + i);
257
+ lines.push('ERROR the real failure is here');
258
+ const out = compress.crushLogToBudget(lines.join('\n'), 500);
259
+ assert.ok(Buffer.byteLength(out, 'utf-8') <= 500);
260
+ assert.match(out, /ERROR the real failure/);
261
+ });
262
+
263
+ test('CMP-23: crushLogToBudget terminates on a single over-budget line with no newline', () => {
264
+ const oneLine = 'ERROR ' + 'x'.repeat(5000);
265
+ const out = compress.crushLogToBudget(oneLine, 500);
266
+ assert.ok(out.length <= 500);
267
+ });
@@ -81,6 +81,39 @@ const DEFAULT_SPAWN = Object.freeze({
81
81
  headless: DEFAULT_SPAWN_HEADLESS,
82
82
  });
83
83
 
84
+ const DEFAULT_COMPRESSION_ELISION = Object.freeze({
85
+ enabled: true,
86
+ ttl_ms: 30 * 60 * 1000,
87
+ });
88
+
89
+ const DEFAULT_COMPRESSION_PROXY = Object.freeze({
90
+ enabled: false,
91
+ });
92
+
93
+ const DEFAULT_COMPRESSION_OUTPUT_STEERING = Object.freeze({
94
+ enabled: false,
95
+ verbosity_profile: 'balanced',
96
+ effort_routing: Object.freeze({
97
+ enabled: false,
98
+ base_effort: null,
99
+ mechanical_effort: 'low',
100
+ }),
101
+ });
102
+
103
+ const DEFAULT_COMPRESSION_CACHE_ALIGN = Object.freeze({
104
+ enabled: false,
105
+ });
106
+
107
+ const DEFAULT_COMPRESSION = Object.freeze({
108
+ enabled: false,
109
+ min_block_bytes: 2048,
110
+ verify_max_bytes: 2000,
111
+ elision: DEFAULT_COMPRESSION_ELISION,
112
+ proxy: DEFAULT_COMPRESSION_PROXY,
113
+ output_steering: DEFAULT_COMPRESSION_OUTPUT_STEERING,
114
+ cache_align: DEFAULT_COMPRESSION_CACHE_ALIGN,
115
+ });
116
+
84
117
  const DEFAULT_MODEL_PROFILE = 'frontier';
85
118
  const DEFAULT_SCOPE = 'local';
86
119
  const DEFAULT_RESPONSE_LANGUAGE = 'en';
@@ -94,6 +127,7 @@ const DEFAULT_CONFIG_TREE = Object.freeze({
94
127
  loop: DEFAULT_LOOP,
95
128
  swarm: DEFAULT_SWARM,
96
129
  spawn: DEFAULT_SPAWN,
130
+ compression: DEFAULT_COMPRESSION,
97
131
  security: DEFAULT_SECURITY,
98
132
  conformance: DEFAULT_CONFORMANCE,
99
133
  learnings: DEFAULT_LEARNINGS,
@@ -145,6 +179,11 @@ module.exports = {
145
179
  DEFAULT_SWARM_CRITIC,
146
180
  DEFAULT_SPAWN,
147
181
  DEFAULT_SPAWN_HEADLESS,
182
+ DEFAULT_COMPRESSION,
183
+ DEFAULT_COMPRESSION_ELISION,
184
+ DEFAULT_COMPRESSION_PROXY,
185
+ DEFAULT_COMPRESSION_OUTPUT_STEERING,
186
+ DEFAULT_COMPRESSION_CACHE_ALIGN,
148
187
  DEFAULT_SECURITY,
149
188
  DEFAULT_CONFORMANCE,
150
189
  DEFAULT_LEARNINGS,
@@ -19,7 +19,7 @@ const SCHEMA = Object.freeze({
19
19
  workflow: {
20
20
  type: 'object', optional: true, shape: {
21
21
  commit_docs: { type: 'boolean', optional: true },
22
- commit_artifacts: { type: 'any', optional: true }, // coerceBool handles string/bool
22
+ commit_artifacts: { type: 'any', optional: true },
23
23
  worktree_isolation: { type: 'boolean', optional: true },
24
24
  research_tools: { type: 'object', shape: 'any', optional: true },
25
25
  text_mode: { type: 'boolean', optional: true },
@@ -71,14 +71,50 @@ const SCHEMA = Object.freeze({
71
71
  },
72
72
  },
73
73
  },
74
+ compression: {
75
+ type: 'object', optional: true, shape: {
76
+ enabled: { type: 'boolean', optional: true },
77
+ min_block_bytes: { type: 'number', optional: true },
78
+ verify_max_bytes:{ type: 'number', optional: true },
79
+ elision: {
80
+ type: 'object', optional: true, shape: {
81
+ enabled: { type: 'boolean', optional: true },
82
+ ttl_ms: { type: 'number', optional: true },
83
+ },
84
+ },
85
+ proxy: {
86
+ type: 'object', optional: true, shape: {
87
+ enabled: { type: 'boolean', optional: true },
88
+ },
89
+ },
90
+ output_steering: {
91
+ type: 'object', optional: true, shape: {
92
+ enabled: { type: 'boolean', optional: true },
93
+ verbosity_profile: { type: 'string', optional: true },
94
+ effort_routing: {
95
+ type: 'object', optional: true, shape: {
96
+ enabled: { type: 'boolean', optional: true },
97
+ base_effort: { type: 'any', optional: true },
98
+ mechanical_effort: { type: 'string', optional: true },
99
+ },
100
+ },
101
+ },
102
+ },
103
+ cache_align: {
104
+ type: 'object', optional: true, shape: {
105
+ enabled: { type: 'boolean', optional: true },
106
+ },
107
+ },
108
+ },
109
+ },
74
110
  security: {
75
111
  type: 'object', optional: true, shape: {
76
112
  enabled: { type: 'boolean', optional: true },
77
113
  scan_on_write: { type: 'boolean', optional: true },
78
114
  review_on_stop: { type: 'boolean', optional: true },
79
115
  review_on_commit: { type: 'boolean', optional: true },
80
- custom_rules_path: { type: 'any', optional: true }, // string | null
81
- guidance_path: { type: 'any', optional: true }, // string | null
116
+ custom_rules_path: { type: 'any', optional: true },
117
+ guidance_path: { type: 'any', optional: true },
82
118
  review_timeout_ms: { type: 'number', optional: true },
83
119
  max_stop_reviews_in_a_row: { type: 'number', optional: true },
84
120
  max_commit_reviews_per_hour:{ type: 'number', optional: true },
@@ -191,7 +227,7 @@ function mergeNewDefaults(existing, defaults) {
191
227
  return _clone(defaults);
192
228
  }
193
229
  if (_typeOf(existing) !== 'object' || _typeOf(defaults) !== 'object') {
194
- return existing; // user wins on type mismatch
230
+ return existing;
195
231
  }
196
232
  const out = _clone(existing);
197
233
  for (const key of Object.keys(defaults)) {