mu-harness 0.28.0 → 0.30.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.
- package/esm/commands/defaults.js +67 -31
- package/esm/session/agent-session.js +1 -0
- package/esm/session/manager.js +1 -0
- package/esm/session/persist.js +1 -0
- package/esm/session/types.d.ts +1 -0
- package/package.json +3 -3
- package/script/commands/defaults.js +67 -31
- package/script/session/agent-session.js +1 -0
- package/script/session/manager.js +1 -0
- package/script/session/persist.js +1 -0
- package/script/session/types.d.ts +1 -0
package/esm/commands/defaults.js
CHANGED
|
@@ -41,17 +41,27 @@ export const createQuitCommand = (onQuit) => ({
|
|
|
41
41
|
},
|
|
42
42
|
});
|
|
43
43
|
const estTokens = (chars) => Math.max(1, Math.round(chars / 4));
|
|
44
|
-
const GRID_COLS =
|
|
45
|
-
const GRID_ROWS =
|
|
44
|
+
const GRID_COLS = 20;
|
|
45
|
+
const GRID_ROWS = 10;
|
|
46
46
|
const GRID_CELLS = GRID_COLS * GRID_ROWS;
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
47
|
+
const GRID_WIDTH = GRID_COLS * 2 - 1; // glyphs joined by single spaces
|
|
48
|
+
const USED = '⛁'; // a filled context cell (Claude Code's /context glyph)
|
|
49
|
+
const FREEG = '⛶'; // a free cell
|
|
50
|
+
const BUFFER_COLOR = '\x1b[93m'; // bright yellow — the compaction reserve
|
|
51
|
+
// ANSI SGR codes — mu's TUI text utils are ANSI-aware so these render in the terminal;
|
|
50
52
|
// the companion strips them (plain text), keeping the labelled breakdown readable.
|
|
51
53
|
const RESET = '\x1b[0m';
|
|
52
54
|
const DIM = '\x1b[2m';
|
|
55
|
+
const BOLD = '\x1b[1m';
|
|
56
|
+
const ITALIC_DIM = '\x1b[2;3m';
|
|
53
57
|
const paint = (s, color) => `${color}${s}${RESET}`;
|
|
54
|
-
const
|
|
58
|
+
const fmtTok = (n) => (n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : `${n}`);
|
|
59
|
+
const pctStr = (n, w) => {
|
|
60
|
+
if (w <= 0)
|
|
61
|
+
return '';
|
|
62
|
+
const p = (n / w) * 100;
|
|
63
|
+
return p > 0 && p < 0.1 ? '<0.1%' : `${p.toFixed(1)}%`;
|
|
64
|
+
};
|
|
55
65
|
/** Extract a `<tag>…</tag>` block (with tags), or '' when absent. */
|
|
56
66
|
function tagBlock(s, tag) {
|
|
57
67
|
const i = s.indexOf(`<${tag}>`);
|
|
@@ -111,42 +121,68 @@ export const createContextCommand = () => ({
|
|
|
111
121
|
}
|
|
112
122
|
return { n: estTokens(text.length), exact: false };
|
|
113
123
|
};
|
|
114
|
-
// label, text, ANSI colour — one per category, in render order.
|
|
124
|
+
// label, text, ANSI colour — one per category, in render order (descriptive labels à la Claude Code).
|
|
115
125
|
const SPEC = [
|
|
116
|
-
['
|
|
117
|
-
['
|
|
118
|
-
['
|
|
119
|
-
['
|
|
120
|
-
['
|
|
121
|
-
['
|
|
122
|
-
['
|
|
123
|
-
['
|
|
126
|
+
['System prompt', sys.agent, '\x1b[36m'], // cyan — the agent prompt
|
|
127
|
+
['Environment', sys.env, '\x1b[33m'], // yellow — the <env> block
|
|
128
|
+
['Instructions', sys.instructions, '\x1b[34m'], // blue — AGENTS.md / CLAUDE.md
|
|
129
|
+
['Memory', sys.memory, '\x1b[35m'], // magenta — MEMORY.md
|
|
130
|
+
['Tools', `${toolSchemas}\n${sys.toolPrompts}`, '\x1b[31m'], // red — schemas + tool prompts
|
|
131
|
+
['You', byRole('user'), '\x1b[32m'], // green — your messages
|
|
132
|
+
['Agent', byRole('assistant'), '\x1b[94m'], // bright blue — assistant replies
|
|
133
|
+
['Tool results', byRole('tool'), '\x1b[90m'], // grey — tool results
|
|
124
134
|
];
|
|
125
135
|
const measured = await Promise.all(SPEC.map(([, text]) => measure(text)));
|
|
126
136
|
const cats = SPEC.map(([label, , color], i) => ({ label, n: measured[i].n, color })).filter((c) => c.n > 0);
|
|
127
137
|
const exact = measured.every((m) => m.exact);
|
|
128
138
|
const total = cats.reduce((s, c) => s + c.n, 0);
|
|
129
139
|
const window = (await ctx.session?.contextWindow?.()) ?? 0;
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
140
|
+
const buffer = window > 0 ? Math.min(Math.round(window * 0.2), Math.max(0, window - total)) : 0;
|
|
141
|
+
const free = Math.max(0, window - total - buffer);
|
|
142
|
+
const mark = (n) => (exact ? fmtTok(n) : `~${fmtTok(n)}`);
|
|
143
|
+
const model = ctx.session?.model ?? '';
|
|
144
|
+
// Right-hand info column — model, totals, then per-category usage (Claude Code's /context).
|
|
145
|
+
const info = [];
|
|
146
|
+
if (model) {
|
|
147
|
+
info.push(model.split('/').pop() ?? model);
|
|
148
|
+
info.push(paint(model, DIM));
|
|
149
|
+
}
|
|
150
|
+
if (window > 0)
|
|
151
|
+
info.push(paint(`${mark(total)}/${fmtTok(window)} tokens (${pctStr(total, window)})`, DIM));
|
|
152
|
+
info.push('');
|
|
153
|
+
info.push(paint('Estimated usage by category', ITALIC_DIM));
|
|
154
|
+
for (const c of cats) {
|
|
155
|
+
info.push(`${paint(USED, c.color)} ${c.label}: ${paint(`${mark(c.n)} tokens (${pctStr(c.n, window)})`, DIM)}`);
|
|
156
|
+
}
|
|
157
|
+
if (window > 0) {
|
|
158
|
+
info.push(`${paint(USED, BUFFER_COLOR)} Compaction buffer: ${paint(`${fmtTok(buffer)} tokens (${pctStr(buffer, window)})`, DIM)}`);
|
|
159
|
+
info.push(`${paint(FREEG, DIM)} Free space: ${paint(`${fmtTok(free)} (${pctStr(free, window)})`, DIM)}`);
|
|
160
|
+
}
|
|
161
|
+
const lines = [`${BOLD}Context Usage${RESET}`];
|
|
137
162
|
if (window > 0) {
|
|
163
|
+
// Grid: category cells, then free, with the compaction buffer at the very end.
|
|
138
164
|
const cellTokens = Math.max(1, window / GRID_CELLS);
|
|
165
|
+
const ratio = (n) => (n <= 0 ? 0 : Math.max(1, Math.round(n / cellTokens)));
|
|
139
166
|
const cells = [];
|
|
140
|
-
for (const c of cats)
|
|
141
|
-
for (let i = 0; i <
|
|
142
|
-
cells.push(paint(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
167
|
+
for (const c of cats)
|
|
168
|
+
for (let i = 0; i < ratio(c.n) && cells.length < GRID_CELLS; i++)
|
|
169
|
+
cells.push(paint(USED, c.color));
|
|
170
|
+
const bufCells = Math.min(ratio(buffer), Math.max(0, GRID_CELLS - cells.length));
|
|
171
|
+
const freeCells = Math.max(0, GRID_CELLS - cells.length - bufCells);
|
|
172
|
+
for (let i = 0; i < freeCells; i++)
|
|
173
|
+
cells.push(paint(FREEG, DIM));
|
|
174
|
+
for (let i = 0; i < bufCells; i++)
|
|
175
|
+
cells.push(paint(USED, BUFFER_COLOR));
|
|
146
176
|
cells.length = GRID_CELLS;
|
|
147
|
-
|
|
148
|
-
for (let r = 0; r < GRID_ROWS; r++)
|
|
149
|
-
|
|
177
|
+
// Side-by-side: 20×10 grid (space-separated glyphs) on the left, info column on the right.
|
|
178
|
+
for (let r = 0; r < Math.max(GRID_ROWS, info.length); r++) {
|
|
179
|
+
const gridRow = r < GRID_ROWS ? cells.slice(r * GRID_COLS, (r + 1) * GRID_COLS).join(' ') : ' '.repeat(GRID_WIDTH);
|
|
180
|
+
lines.push(`${gridRow} ${info[r] ?? ''}`.trimEnd());
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
for (const l of info)
|
|
185
|
+
lines.push(l.trimEnd());
|
|
150
186
|
}
|
|
151
187
|
return { ok: true, output: lines.join('\n') };
|
|
152
188
|
},
|
package/esm/session/manager.js
CHANGED
|
@@ -12,6 +12,7 @@ const onFirstMessage = (session, fire) => {
|
|
|
12
12
|
get tools() {
|
|
13
13
|
return session.tools;
|
|
14
14
|
},
|
|
15
|
+
model: session.model,
|
|
15
16
|
assembleRequest: session.assembleRequest?.bind(session),
|
|
16
17
|
countTokens: session.countTokens?.bind(session),
|
|
17
18
|
contextWindow: session.contextWindow?.bind(session),
|
package/esm/session/persist.js
CHANGED
|
@@ -10,6 +10,7 @@ export const persistTo = (store, session, persisted = 0) => {
|
|
|
10
10
|
get tools() {
|
|
11
11
|
return session.tools;
|
|
12
12
|
},
|
|
13
|
+
model: session.model,
|
|
13
14
|
assembleRequest: session.assembleRequest?.bind(session),
|
|
14
15
|
countTokens: session.countTokens?.bind(session),
|
|
15
16
|
contextWindow: session.contextWindow?.bind(session),
|
package/esm/session/types.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface AssembledRequest {
|
|
|
19
19
|
}
|
|
20
20
|
export interface AgentSession {
|
|
21
21
|
readonly id: string;
|
|
22
|
+
readonly model?: string;
|
|
22
23
|
readonly messages: readonly Message[];
|
|
23
24
|
readonly tools: readonly Tool[];
|
|
24
25
|
/** Assemble the request from the CURRENT in-memory session — what the next turn would send. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mu-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.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.
|
|
27
|
-
"mu-tui": "^0.
|
|
26
|
+
"mu-core": "^0.30.0",
|
|
27
|
+
"mu-tui": "^0.30.0",
|
|
28
28
|
"ws": "^8.18.0"
|
|
29
29
|
},
|
|
30
30
|
"_generatedBy": "dnt@dev",
|
|
@@ -48,17 +48,27 @@ 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 =
|
|
52
|
-
const GRID_ROWS =
|
|
51
|
+
const GRID_COLS = 20;
|
|
52
|
+
const GRID_ROWS = 10;
|
|
53
53
|
const GRID_CELLS = GRID_COLS * GRID_ROWS;
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
54
|
+
const GRID_WIDTH = GRID_COLS * 2 - 1; // glyphs joined by single spaces
|
|
55
|
+
const USED = '⛁'; // a filled context cell (Claude Code's /context glyph)
|
|
56
|
+
const FREEG = '⛶'; // a free cell
|
|
57
|
+
const BUFFER_COLOR = '\x1b[93m'; // bright yellow — the compaction reserve
|
|
58
|
+
// ANSI SGR codes — mu's TUI text utils are ANSI-aware so these render in the terminal;
|
|
57
59
|
// the companion strips them (plain text), keeping the labelled breakdown readable.
|
|
58
60
|
const RESET = '\x1b[0m';
|
|
59
61
|
const DIM = '\x1b[2m';
|
|
62
|
+
const BOLD = '\x1b[1m';
|
|
63
|
+
const ITALIC_DIM = '\x1b[2;3m';
|
|
60
64
|
const paint = (s, color) => `${color}${s}${RESET}`;
|
|
61
|
-
const
|
|
65
|
+
const fmtTok = (n) => (n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : `${n}`);
|
|
66
|
+
const pctStr = (n, w) => {
|
|
67
|
+
if (w <= 0)
|
|
68
|
+
return '';
|
|
69
|
+
const p = (n / w) * 100;
|
|
70
|
+
return p > 0 && p < 0.1 ? '<0.1%' : `${p.toFixed(1)}%`;
|
|
71
|
+
};
|
|
62
72
|
/** Extract a `<tag>…</tag>` block (with tags), or '' when absent. */
|
|
63
73
|
function tagBlock(s, tag) {
|
|
64
74
|
const i = s.indexOf(`<${tag}>`);
|
|
@@ -118,42 +128,68 @@ const createContextCommand = () => ({
|
|
|
118
128
|
}
|
|
119
129
|
return { n: estTokens(text.length), exact: false };
|
|
120
130
|
};
|
|
121
|
-
// label, text, ANSI colour — one per category, in render order.
|
|
131
|
+
// label, text, ANSI colour — one per category, in render order (descriptive labels à la Claude Code).
|
|
122
132
|
const SPEC = [
|
|
123
|
-
['
|
|
124
|
-
['
|
|
125
|
-
['
|
|
126
|
-
['
|
|
127
|
-
['
|
|
128
|
-
['
|
|
129
|
-
['
|
|
130
|
-
['
|
|
133
|
+
['System prompt', sys.agent, '\x1b[36m'], // cyan — the agent prompt
|
|
134
|
+
['Environment', sys.env, '\x1b[33m'], // yellow — the <env> block
|
|
135
|
+
['Instructions', sys.instructions, '\x1b[34m'], // blue — AGENTS.md / CLAUDE.md
|
|
136
|
+
['Memory', sys.memory, '\x1b[35m'], // magenta — MEMORY.md
|
|
137
|
+
['Tools', `${toolSchemas}\n${sys.toolPrompts}`, '\x1b[31m'], // red — schemas + tool prompts
|
|
138
|
+
['You', byRole('user'), '\x1b[32m'], // green — your messages
|
|
139
|
+
['Agent', byRole('assistant'), '\x1b[94m'], // bright blue — assistant replies
|
|
140
|
+
['Tool results', byRole('tool'), '\x1b[90m'], // grey — tool results
|
|
131
141
|
];
|
|
132
142
|
const measured = await Promise.all(SPEC.map(([, text]) => measure(text)));
|
|
133
143
|
const cats = SPEC.map(([label, , color], i) => ({ label, n: measured[i].n, color })).filter((c) => c.n > 0);
|
|
134
144
|
const exact = measured.every((m) => m.exact);
|
|
135
145
|
const total = cats.reduce((s, c) => s + c.n, 0);
|
|
136
146
|
const window = (await ctx.session?.contextWindow?.()) ?? 0;
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
147
|
+
const buffer = window > 0 ? Math.min(Math.round(window * 0.2), Math.max(0, window - total)) : 0;
|
|
148
|
+
const free = Math.max(0, window - total - buffer);
|
|
149
|
+
const mark = (n) => (exact ? fmtTok(n) : `~${fmtTok(n)}`);
|
|
150
|
+
const model = ctx.session?.model ?? '';
|
|
151
|
+
// Right-hand info column — model, totals, then per-category usage (Claude Code's /context).
|
|
152
|
+
const info = [];
|
|
153
|
+
if (model) {
|
|
154
|
+
info.push(model.split('/').pop() ?? model);
|
|
155
|
+
info.push(paint(model, DIM));
|
|
156
|
+
}
|
|
157
|
+
if (window > 0)
|
|
158
|
+
info.push(paint(`${mark(total)}/${fmtTok(window)} tokens (${pctStr(total, window)})`, DIM));
|
|
159
|
+
info.push('');
|
|
160
|
+
info.push(paint('Estimated usage by category', ITALIC_DIM));
|
|
161
|
+
for (const c of cats) {
|
|
162
|
+
info.push(`${paint(USED, c.color)} ${c.label}: ${paint(`${mark(c.n)} tokens (${pctStr(c.n, window)})`, DIM)}`);
|
|
163
|
+
}
|
|
164
|
+
if (window > 0) {
|
|
165
|
+
info.push(`${paint(USED, BUFFER_COLOR)} Compaction buffer: ${paint(`${fmtTok(buffer)} tokens (${pctStr(buffer, window)})`, DIM)}`);
|
|
166
|
+
info.push(`${paint(FREEG, DIM)} Free space: ${paint(`${fmtTok(free)} (${pctStr(free, window)})`, DIM)}`);
|
|
167
|
+
}
|
|
168
|
+
const lines = [`${BOLD}Context Usage${RESET}`];
|
|
144
169
|
if (window > 0) {
|
|
170
|
+
// Grid: category cells, then free, with the compaction buffer at the very end.
|
|
145
171
|
const cellTokens = Math.max(1, window / GRID_CELLS);
|
|
172
|
+
const ratio = (n) => (n <= 0 ? 0 : Math.max(1, Math.round(n / cellTokens)));
|
|
146
173
|
const cells = [];
|
|
147
|
-
for (const c of cats)
|
|
148
|
-
for (let i = 0; i <
|
|
149
|
-
cells.push(paint(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
174
|
+
for (const c of cats)
|
|
175
|
+
for (let i = 0; i < ratio(c.n) && cells.length < GRID_CELLS; i++)
|
|
176
|
+
cells.push(paint(USED, c.color));
|
|
177
|
+
const bufCells = Math.min(ratio(buffer), Math.max(0, GRID_CELLS - cells.length));
|
|
178
|
+
const freeCells = Math.max(0, GRID_CELLS - cells.length - bufCells);
|
|
179
|
+
for (let i = 0; i < freeCells; i++)
|
|
180
|
+
cells.push(paint(FREEG, DIM));
|
|
181
|
+
for (let i = 0; i < bufCells; i++)
|
|
182
|
+
cells.push(paint(USED, BUFFER_COLOR));
|
|
153
183
|
cells.length = GRID_CELLS;
|
|
154
|
-
|
|
155
|
-
for (let r = 0; r < GRID_ROWS; r++)
|
|
156
|
-
|
|
184
|
+
// Side-by-side: 20×10 grid (space-separated glyphs) on the left, info column on the right.
|
|
185
|
+
for (let r = 0; r < Math.max(GRID_ROWS, info.length); r++) {
|
|
186
|
+
const gridRow = r < GRID_ROWS ? cells.slice(r * GRID_COLS, (r + 1) * GRID_COLS).join(' ') : ' '.repeat(GRID_WIDTH);
|
|
187
|
+
lines.push(`${gridRow} ${info[r] ?? ''}`.trimEnd());
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
for (const l of info)
|
|
192
|
+
lines.push(l.trimEnd());
|
|
157
193
|
}
|
|
158
194
|
return { ok: true, output: lines.join('\n') };
|
|
159
195
|
},
|
|
@@ -15,6 +15,7 @@ const onFirstMessage = (session, fire) => {
|
|
|
15
15
|
get tools() {
|
|
16
16
|
return session.tools;
|
|
17
17
|
},
|
|
18
|
+
model: session.model,
|
|
18
19
|
assembleRequest: session.assembleRequest?.bind(session),
|
|
19
20
|
countTokens: session.countTokens?.bind(session),
|
|
20
21
|
contextWindow: session.contextWindow?.bind(session),
|
|
@@ -13,6 +13,7 @@ const persistTo = (store, session, persisted = 0) => {
|
|
|
13
13
|
get tools() {
|
|
14
14
|
return session.tools;
|
|
15
15
|
},
|
|
16
|
+
model: session.model,
|
|
16
17
|
assembleRequest: session.assembleRequest?.bind(session),
|
|
17
18
|
countTokens: session.countTokens?.bind(session),
|
|
18
19
|
contextWindow: session.contextWindow?.bind(session),
|
|
@@ -19,6 +19,7 @@ export interface AssembledRequest {
|
|
|
19
19
|
}
|
|
20
20
|
export interface AgentSession {
|
|
21
21
|
readonly id: string;
|
|
22
|
+
readonly model?: string;
|
|
22
23
|
readonly messages: readonly Message[];
|
|
23
24
|
readonly tools: readonly Tool[];
|
|
24
25
|
/** Assemble the request from the CURRENT in-memory session — what the next turn would send. */
|