mu-harness 0.28.0 → 0.29.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.
@@ -41,16 +41,24 @@ export const createQuitCommand = (onQuit) => ({
41
41
  },
42
42
  });
43
43
  const estTokens = (chars) => Math.max(1, Math.round(chars / 4));
44
- const GRID_COLS = 24;
45
- const GRID_ROWS = 8;
44
+ const GRID_COLS = 20;
45
+ const GRID_ROWS = 10;
46
46
  const GRID_CELLS = GRID_COLS * GRID_ROWS;
47
47
  const BLOCK = '█';
48
48
  const FREE = '·';
49
+ const BUFFER_COLOR = '\x1b[93m'; // bright yellow — the compaction reserve
49
50
  // ANSI SGR colours — mu's TUI text utils are ANSI-aware so these render in the terminal;
50
51
  // the companion strips them (plain text), keeping the labelled breakdown readable.
51
52
  const RESET = '\x1b[0m';
52
53
  const DIM = '\x1b[2m';
53
54
  const paint = (s, color) => `${color}${s}${RESET}`;
55
+ const fmtTok = (n) => (n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : `${n}`);
56
+ const pctStr = (n, w) => {
57
+ if (w <= 0)
58
+ return '';
59
+ const p = (n / w) * 100;
60
+ return p > 0 && p < 0.1 ? '<0.1%' : `${p.toFixed(1)}%`;
61
+ };
54
62
  const fillColor = (pct) => (pct >= 80 ? '\x1b[31m' : pct >= 50 ? '\x1b[33m' : '\x1b[32m');
55
63
  /** Extract a `<tag>…</tag>` block (with tags), or '' when absent. */
56
64
  function tagBlock(s, tag) {
@@ -127,26 +135,46 @@ export const createContextCommand = () => ({
127
135
  const exact = measured.every((m) => m.exact);
128
136
  const total = cats.reduce((s, c) => s + c.n, 0);
129
137
  const window = (await ctx.session?.contextWindow?.()) ?? 0;
130
- const mark = (n) => (exact ? `${n}` : `~${n}`);
131
- const lines = [`context — tokens (${exact ? 'exact, model tokenizer' : 'estimated ≈ chars/4'}):`];
132
- for (const c of cats)
133
- lines.push(` ${paint(BLOCK, c.color)} ${c.label.padEnd(13)} ${mark(c.n)}`);
138
+ const buffer = window > 0 ? Math.min(Math.round(window * 0.2), Math.max(0, window - total)) : 0;
139
+ const free = Math.max(0, window - total - buffer);
140
+ const mark = (n) => (exact ? fmtTok(n) : `~${fmtTok(n)}`);
134
141
  const pctNum = window ? Math.round((total / window) * 100) : 0;
135
- const pct = window ? ` / ${window} ${paint(`(${pctNum}%)`, fillColor(pctNum))}` : '';
136
- lines.push(` ${' '.repeat(15)}── ${mark(total)}${pct}`);
142
+ // Legend rows (categories, then buffer + free), Claude Code / oh-my-pi style.
143
+ const rows = cats.map((c) => ({ marker: paint(BLOCK, c.color), label: c.label, n: c.n }));
137
144
  if (window > 0) {
145
+ rows.push({ marker: paint(BLOCK, BUFFER_COLOR), label: 'buffer', n: buffer });
146
+ rows.push({ marker: paint(FREE, DIM), label: 'free', n: free });
147
+ }
148
+ const legend = rows.map((r) => `${r.marker} ${r.label.padEnd(13)} ${mark(r.n).padStart(7)} ${pctStr(r.n, window).padStart(6)}`);
149
+ const header = window
150
+ ? `context · ${mark(total)} / ${fmtTok(window)} ${paint(`(${pctStr(total, window)})`, fillColor(pctNum))}`
151
+ : `context · ${mark(total)} tokens (no model context window)`;
152
+ const lines = [header, ''];
153
+ if (window > 0) {
154
+ // Build the grid: category cells, then free, with the compaction buffer at the very end.
138
155
  const cellTokens = Math.max(1, window / GRID_CELLS);
156
+ const ratio = (n) => (n <= 0 ? 0 : Math.max(1, Math.round(n / cellTokens)));
139
157
  const cells = [];
140
- for (const c of cats) {
141
- for (let i = 0; i < Math.round(c.n / cellTokens) && cells.length < GRID_CELLS; i++)
158
+ for (const c of cats)
159
+ for (let i = 0; i < ratio(c.n) && cells.length < GRID_CELLS; i++)
142
160
  cells.push(paint(BLOCK, c.color));
143
- }
144
- while (cells.length < GRID_CELLS)
161
+ const bufCells = Math.min(ratio(buffer), Math.max(0, GRID_CELLS - cells.length));
162
+ const freeCells = Math.max(0, GRID_CELLS - cells.length - bufCells);
163
+ for (let i = 0; i < freeCells; i++)
145
164
  cells.push(paint(FREE, DIM));
165
+ for (let i = 0; i < bufCells; i++)
166
+ cells.push(paint(BLOCK, BUFFER_COLOR));
146
167
  cells.length = GRID_CELLS;
147
- lines.push('');
148
- for (let r = 0; r < GRID_ROWS; r++)
149
- lines.push(` ${cells.slice(r * GRID_COLS, (r + 1) * GRID_COLS).join('')}`);
168
+ // Side-by-side: 20×10 grid on the left, the legend on the right.
169
+ const rowsCount = Math.max(GRID_ROWS, legend.length);
170
+ for (let r = 0; r < rowsCount; r++) {
171
+ const gridRow = r < GRID_ROWS ? cells.slice(r * GRID_COLS, (r + 1) * GRID_COLS).join('') : ' '.repeat(GRID_COLS);
172
+ lines.push(` ${gridRow} ${legend[r] ?? ''}`.trimEnd());
173
+ }
174
+ }
175
+ else {
176
+ for (const l of legend)
177
+ lines.push(` ${l.trimEnd()}`);
150
178
  }
151
179
  return { ok: true, output: lines.join('\n') };
152
180
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-harness",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "Agent harness: createHarness wires mu-core into a host — XDG paths, model registry, plugins, disk-loaded agents & skills, sub-agents, sessions (JSONL + SQLite catalog), slash commands, permission/approval hooks, an optional scheduler, and a composable TUI chat app",
5
5
  "license": "MIT",
6
6
  "main": "./script/index.js",
@@ -23,8 +23,8 @@
23
23
  "@swc/wasm-typescript": "^1.15.0",
24
24
  "cli-highlight": "^2.1.11",
25
25
  "croner": "^9.0.0",
26
- "mu-core": "^0.28.0",
27
- "mu-tui": "^0.28.0",
26
+ "mu-core": "^0.29.0",
27
+ "mu-tui": "^0.29.0",
28
28
  "ws": "^8.18.0"
29
29
  },
30
30
  "_generatedBy": "dnt@dev",
@@ -48,16 +48,24 @@ const createQuitCommand = (onQuit) => ({
48
48
  });
49
49
  exports.createQuitCommand = createQuitCommand;
50
50
  const estTokens = (chars) => Math.max(1, Math.round(chars / 4));
51
- const GRID_COLS = 24;
52
- const GRID_ROWS = 8;
51
+ const GRID_COLS = 20;
52
+ const GRID_ROWS = 10;
53
53
  const GRID_CELLS = GRID_COLS * GRID_ROWS;
54
54
  const BLOCK = '█';
55
55
  const FREE = '·';
56
+ const BUFFER_COLOR = '\x1b[93m'; // bright yellow — the compaction reserve
56
57
  // ANSI SGR colours — mu's TUI text utils are ANSI-aware so these render in the terminal;
57
58
  // the companion strips them (plain text), keeping the labelled breakdown readable.
58
59
  const RESET = '\x1b[0m';
59
60
  const DIM = '\x1b[2m';
60
61
  const paint = (s, color) => `${color}${s}${RESET}`;
62
+ const fmtTok = (n) => (n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : `${n}`);
63
+ const pctStr = (n, w) => {
64
+ if (w <= 0)
65
+ return '';
66
+ const p = (n / w) * 100;
67
+ return p > 0 && p < 0.1 ? '<0.1%' : `${p.toFixed(1)}%`;
68
+ };
61
69
  const fillColor = (pct) => (pct >= 80 ? '\x1b[31m' : pct >= 50 ? '\x1b[33m' : '\x1b[32m');
62
70
  /** Extract a `<tag>…</tag>` block (with tags), or '' when absent. */
63
71
  function tagBlock(s, tag) {
@@ -134,26 +142,46 @@ const createContextCommand = () => ({
134
142
  const exact = measured.every((m) => m.exact);
135
143
  const total = cats.reduce((s, c) => s + c.n, 0);
136
144
  const window = (await ctx.session?.contextWindow?.()) ?? 0;
137
- const mark = (n) => (exact ? `${n}` : `~${n}`);
138
- const lines = [`context — tokens (${exact ? 'exact, model tokenizer' : 'estimated ≈ chars/4'}):`];
139
- for (const c of cats)
140
- lines.push(` ${paint(BLOCK, c.color)} ${c.label.padEnd(13)} ${mark(c.n)}`);
145
+ const buffer = window > 0 ? Math.min(Math.round(window * 0.2), Math.max(0, window - total)) : 0;
146
+ const free = Math.max(0, window - total - buffer);
147
+ const mark = (n) => (exact ? fmtTok(n) : `~${fmtTok(n)}`);
141
148
  const pctNum = window ? Math.round((total / window) * 100) : 0;
142
- const pct = window ? ` / ${window} ${paint(`(${pctNum}%)`, fillColor(pctNum))}` : '';
143
- lines.push(` ${' '.repeat(15)}── ${mark(total)}${pct}`);
149
+ // Legend rows (categories, then buffer + free), Claude Code / oh-my-pi style.
150
+ const rows = cats.map((c) => ({ marker: paint(BLOCK, c.color), label: c.label, n: c.n }));
144
151
  if (window > 0) {
152
+ rows.push({ marker: paint(BLOCK, BUFFER_COLOR), label: 'buffer', n: buffer });
153
+ rows.push({ marker: paint(FREE, DIM), label: 'free', n: free });
154
+ }
155
+ const legend = rows.map((r) => `${r.marker} ${r.label.padEnd(13)} ${mark(r.n).padStart(7)} ${pctStr(r.n, window).padStart(6)}`);
156
+ const header = window
157
+ ? `context · ${mark(total)} / ${fmtTok(window)} ${paint(`(${pctStr(total, window)})`, fillColor(pctNum))}`
158
+ : `context · ${mark(total)} tokens (no model context window)`;
159
+ const lines = [header, ''];
160
+ if (window > 0) {
161
+ // Build the grid: category cells, then free, with the compaction buffer at the very end.
145
162
  const cellTokens = Math.max(1, window / GRID_CELLS);
163
+ const ratio = (n) => (n <= 0 ? 0 : Math.max(1, Math.round(n / cellTokens)));
146
164
  const cells = [];
147
- for (const c of cats) {
148
- for (let i = 0; i < Math.round(c.n / cellTokens) && cells.length < GRID_CELLS; i++)
165
+ for (const c of cats)
166
+ for (let i = 0; i < ratio(c.n) && cells.length < GRID_CELLS; i++)
149
167
  cells.push(paint(BLOCK, c.color));
150
- }
151
- while (cells.length < GRID_CELLS)
168
+ const bufCells = Math.min(ratio(buffer), Math.max(0, GRID_CELLS - cells.length));
169
+ const freeCells = Math.max(0, GRID_CELLS - cells.length - bufCells);
170
+ for (let i = 0; i < freeCells; i++)
152
171
  cells.push(paint(FREE, DIM));
172
+ for (let i = 0; i < bufCells; i++)
173
+ cells.push(paint(BLOCK, BUFFER_COLOR));
153
174
  cells.length = GRID_CELLS;
154
- lines.push('');
155
- for (let r = 0; r < GRID_ROWS; r++)
156
- lines.push(` ${cells.slice(r * GRID_COLS, (r + 1) * GRID_COLS).join('')}`);
175
+ // Side-by-side: 20×10 grid on the left, the legend on the right.
176
+ const rowsCount = Math.max(GRID_ROWS, legend.length);
177
+ for (let r = 0; r < rowsCount; r++) {
178
+ const gridRow = r < GRID_ROWS ? cells.slice(r * GRID_COLS, (r + 1) * GRID_COLS).join('') : ' '.repeat(GRID_COLS);
179
+ lines.push(` ${gridRow} ${legend[r] ?? ''}`.trimEnd());
180
+ }
181
+ }
182
+ else {
183
+ for (const l of legend)
184
+ lines.push(` ${l.trimEnd()}`);
157
185
  }
158
186
  return { ok: true, output: lines.join('\n') };
159
187
  },