mu-harness 0.16.16 → 0.16.17
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/tui/chat/ChatApp.d.ts +12 -2
- package/esm/tui/chat/ChatApp.js +36 -19
- package/esm/tui/chat/status.d.ts +2 -2
- package/esm/tui/chat/status.js +2 -4
- package/package.json +3 -3
- package/script/tui/chat/ChatApp.d.ts +12 -2
- package/script/tui/chat/ChatApp.js +36 -19
- package/script/tui/chat/status.d.ts +2 -2
- package/script/tui/chat/status.js +2 -4
|
@@ -53,6 +53,15 @@ export interface ChatHost {
|
|
|
53
53
|
* (a splash). Cleared once the conversation starts.
|
|
54
54
|
*/
|
|
55
55
|
banner?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Lean input presentation: drop the surface-background input frame, the
|
|
58
|
+
* model/provider/agent footer inside the input, and the context readout on
|
|
59
|
+
* the status bar — leaving a bare prompt + editor. The {@link ChatHost.banner}
|
|
60
|
+
* splash is independent and still shown if set. Hosts that want the full
|
|
61
|
+
* information-rich input (surface frame, model · provider · @agent footer,
|
|
62
|
+
* token/context usage in the status line) leave this unset (the default).
|
|
63
|
+
*/
|
|
64
|
+
minimal?: boolean;
|
|
56
65
|
onExit(code: number): void;
|
|
57
66
|
}
|
|
58
67
|
export declare class ChatApp {
|
|
@@ -69,6 +78,7 @@ export declare class ChatApp {
|
|
|
69
78
|
private session;
|
|
70
79
|
private readonly features;
|
|
71
80
|
private readonly banner;
|
|
81
|
+
private readonly minimal;
|
|
72
82
|
private unsubscribe;
|
|
73
83
|
private unsubscribeTheme;
|
|
74
84
|
private unsubscribeSubAgents;
|
|
@@ -161,8 +171,8 @@ export declare class ChatApp {
|
|
|
161
171
|
private startSpinner;
|
|
162
172
|
private stopSpinner;
|
|
163
173
|
private loadModels;
|
|
164
|
-
/**
|
|
165
|
-
private
|
|
174
|
+
/** Styled model id + provider + active agent, shown in the input container footer. */
|
|
175
|
+
private modelLabel;
|
|
166
176
|
private updateSpeaker;
|
|
167
177
|
private promptGlyph;
|
|
168
178
|
private inputPanel;
|
package/esm/tui/chat/ChatApp.js
CHANGED
|
@@ -77,13 +77,14 @@ export class ChatApp {
|
|
|
77
77
|
session;
|
|
78
78
|
features;
|
|
79
79
|
banner;
|
|
80
|
+
minimal;
|
|
80
81
|
unsubscribe;
|
|
81
82
|
unsubscribeTheme;
|
|
82
83
|
unsubscribeSubAgents;
|
|
83
84
|
runUnsubs = new Set();
|
|
84
85
|
activeRuns = new Set();
|
|
85
86
|
mentionAc;
|
|
86
|
-
status = { label: 'ready', busy: false, spinnerTick: 0, context: ''
|
|
87
|
+
status = { label: 'ready', busy: false, spinnerTick: 0, context: '' };
|
|
87
88
|
running = false;
|
|
88
89
|
queue = [];
|
|
89
90
|
pendingShell = [];
|
|
@@ -113,6 +114,7 @@ export class ChatApp {
|
|
|
113
114
|
this.session = host.session;
|
|
114
115
|
this.features = host.features ?? {};
|
|
115
116
|
this.banner = host.banner;
|
|
117
|
+
this.minimal = host.minimal ?? false;
|
|
116
118
|
this.transcript.thinkingVisible = host.initialThinking;
|
|
117
119
|
this.history = host.history?.load() ?? [];
|
|
118
120
|
this.historyIndex = this.history.length;
|
|
@@ -1051,15 +1053,24 @@ export class ChatApp {
|
|
|
1051
1053
|
// backend may be unreachable; surfaced on first send
|
|
1052
1054
|
}
|
|
1053
1055
|
}
|
|
1054
|
-
/**
|
|
1055
|
-
|
|
1056
|
+
/** Styled model id + provider + active agent, shown in the input container footer. */
|
|
1057
|
+
modelLabel() {
|
|
1056
1058
|
const ref = this.host.modelRef();
|
|
1057
1059
|
const slash = ref.indexOf('/');
|
|
1058
1060
|
const id = slash >= 0 ? ref.slice(slash + 1) : ref;
|
|
1059
1061
|
const providerName = slash >= 0 ? ref.slice(0, slash) : '';
|
|
1060
1062
|
const model = this.models.find((m) => m.id === id);
|
|
1061
1063
|
const provider = model?.ownedBy ?? providerName;
|
|
1062
|
-
|
|
1064
|
+
const theme = this.theme();
|
|
1065
|
+
const bold = styleToAnsi({ fg: theme.colors.text, bold: true });
|
|
1066
|
+
const dim = styleToAnsi({ fg: theme.colors.textMuted });
|
|
1067
|
+
const head = provider ? `${bold}${id}${RESET} ${dim}${provider}${RESET}` : `${bold}${id}${RESET}`;
|
|
1068
|
+
const agent = this.host.agentRef();
|
|
1069
|
+
if (!agent)
|
|
1070
|
+
return head;
|
|
1071
|
+
const hex = asHexColor(this.host.agentColor());
|
|
1072
|
+
const agentSgr = hex ? styleToAnsi({ fg: hex, bold: true }) : dim;
|
|
1073
|
+
return `${head} ${dim}·${RESET} ${agentSgr}@${agent}${RESET}`;
|
|
1063
1074
|
}
|
|
1064
1075
|
updateSpeaker() {
|
|
1065
1076
|
this.transcript.speaker = { name: this.host.agentRef(), color: asHexColor(this.host.agentColor()) };
|
|
@@ -1078,19 +1089,27 @@ export class ChatApp {
|
|
|
1078
1089
|
}
|
|
1079
1090
|
inputPanel() {
|
|
1080
1091
|
const inner = this.approvalView() ?? this.editorInner();
|
|
1092
|
+
if (this.minimal)
|
|
1093
|
+
return box(inner, { padding: 0 });
|
|
1081
1094
|
return box(inner, { background: this.theme().colors.surface, padding: 1 });
|
|
1082
1095
|
}
|
|
1083
1096
|
editorInner() {
|
|
1084
1097
|
const prompt = this.promptGlyph();
|
|
1085
1098
|
const editor = this.editor;
|
|
1086
1099
|
const editorRows = editor.rows();
|
|
1100
|
+
const label = this.minimal ? '' : this.modelLabel();
|
|
1087
1101
|
return {
|
|
1088
1102
|
render: (s) => {
|
|
1089
1103
|
if (s.width <= 0 || s.height <= 0)
|
|
1090
1104
|
return;
|
|
1091
1105
|
s.text(0, 0, prompt);
|
|
1092
|
-
const
|
|
1106
|
+
const reserve = label ? 2 : 1;
|
|
1107
|
+
const rows = Math.min(editorRows, Math.max(1, s.height - reserve));
|
|
1093
1108
|
s.child(editor, { x: PROMPT_WIDTH, y: 0, width: Math.max(1, s.width - PROMPT_WIDTH), height: rows });
|
|
1109
|
+
if (label) {
|
|
1110
|
+
const labelRow = rows + 1;
|
|
1111
|
+
s.text(0, labelRow, visibleWidth(label) > s.width ? truncateToWidth(label, s.width) : label);
|
|
1112
|
+
}
|
|
1094
1113
|
},
|
|
1095
1114
|
};
|
|
1096
1115
|
}
|
|
@@ -1150,7 +1169,7 @@ export class ChatApp {
|
|
|
1150
1169
|
return children;
|
|
1151
1170
|
}
|
|
1152
1171
|
statusBar() {
|
|
1153
|
-
this.status.
|
|
1172
|
+
this.status.minimal = this.minimal;
|
|
1154
1173
|
return statusComponent(this.status, this.theme());
|
|
1155
1174
|
}
|
|
1156
1175
|
dock() {
|
|
@@ -1279,19 +1298,17 @@ export class ChatApp {
|
|
|
1279
1298
|
const focused = this.focusedSub();
|
|
1280
1299
|
const showBanner = this.banner !== undefined && this.transcript.entries.length === 0 && !focused;
|
|
1281
1300
|
const spacer = { render: () => { } };
|
|
1282
|
-
const inner = focused
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
// Conversation: transcript fills, input docked at the bottom.
|
|
1294
|
-
: column([flex(this.scroll), this.dock()]);
|
|
1301
|
+
const inner = focused ? this.subAgentView(focused) : showBanner
|
|
1302
|
+
// Splash: banner + a centered, width-limited minimal input; status pinned at the bottom.
|
|
1303
|
+
? column([
|
|
1304
|
+
flex(spacer),
|
|
1305
|
+
this.bannerBlock(),
|
|
1306
|
+
this.centered(column(this.inputGroup()), SPLASH_INPUT_WIDTH),
|
|
1307
|
+
flex(spacer),
|
|
1308
|
+
this.statusBar(),
|
|
1309
|
+
])
|
|
1310
|
+
// Conversation: transcript fills, input docked at the bottom.
|
|
1311
|
+
: column([flex(this.scroll), this.dock()]);
|
|
1295
1312
|
return {
|
|
1296
1313
|
render: (s) => {
|
|
1297
1314
|
s.fill({ x: 0, y: 0, width: s.width, height: s.height }, this.theme().colors.background);
|
package/esm/tui/chat/status.d.ts
CHANGED
|
@@ -8,8 +8,8 @@ export interface StatusState {
|
|
|
8
8
|
busy: boolean;
|
|
9
9
|
spinnerTick: number;
|
|
10
10
|
context: string;
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/** Lean mode: hide the context readout, leaving only a busy spinner. */
|
|
12
|
+
minimal?: boolean;
|
|
13
13
|
}
|
|
14
14
|
export declare function statusFromEvent(event: AgentSessionEvent): string | undefined;
|
|
15
15
|
export declare function statusComponent(state: StatusState, theme: Theme): Component;
|
package/esm/tui/chat/status.js
CHANGED
|
@@ -32,10 +32,8 @@ export function statusComponent(state, theme) {
|
|
|
32
32
|
return;
|
|
33
33
|
const muted = styleToAnsi(theme.styles.muted);
|
|
34
34
|
const spinner = `${muted}${spinnerFrame(state.spinnerTick)}${RESET}`;
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
const left = [model, activity].filter(Boolean).join(`${muted} · ${RESET}`);
|
|
38
|
-
const right = state.context ? `${muted}${state.context}${RESET}` : '';
|
|
35
|
+
const left = state.busy ? (state.label ? `${spinner} ${muted}${state.label}${RESET}` : spinner) : '';
|
|
36
|
+
const right = state.minimal ? '' : (state.context ? `${muted}${state.context}${RESET}` : '');
|
|
39
37
|
if (!left && !right) {
|
|
40
38
|
s.text(0, 0, '');
|
|
41
39
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mu-harness",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.17",
|
|
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.16.
|
|
27
|
-
"mu-tui": "^0.16.
|
|
26
|
+
"mu-core": "^0.16.17",
|
|
27
|
+
"mu-tui": "^0.16.17"
|
|
28
28
|
},
|
|
29
29
|
"_generatedBy": "dnt@dev",
|
|
30
30
|
"types": "./esm/index.d.ts"
|
|
@@ -53,6 +53,15 @@ export interface ChatHost {
|
|
|
53
53
|
* (a splash). Cleared once the conversation starts.
|
|
54
54
|
*/
|
|
55
55
|
banner?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Lean input presentation: drop the surface-background input frame, the
|
|
58
|
+
* model/provider/agent footer inside the input, and the context readout on
|
|
59
|
+
* the status bar — leaving a bare prompt + editor. The {@link ChatHost.banner}
|
|
60
|
+
* splash is independent and still shown if set. Hosts that want the full
|
|
61
|
+
* information-rich input (surface frame, model · provider · @agent footer,
|
|
62
|
+
* token/context usage in the status line) leave this unset (the default).
|
|
63
|
+
*/
|
|
64
|
+
minimal?: boolean;
|
|
56
65
|
onExit(code: number): void;
|
|
57
66
|
}
|
|
58
67
|
export declare class ChatApp {
|
|
@@ -69,6 +78,7 @@ export declare class ChatApp {
|
|
|
69
78
|
private session;
|
|
70
79
|
private readonly features;
|
|
71
80
|
private readonly banner;
|
|
81
|
+
private readonly minimal;
|
|
72
82
|
private unsubscribe;
|
|
73
83
|
private unsubscribeTheme;
|
|
74
84
|
private unsubscribeSubAgents;
|
|
@@ -161,8 +171,8 @@ export declare class ChatApp {
|
|
|
161
171
|
private startSpinner;
|
|
162
172
|
private stopSpinner;
|
|
163
173
|
private loadModels;
|
|
164
|
-
/**
|
|
165
|
-
private
|
|
174
|
+
/** Styled model id + provider + active agent, shown in the input container footer. */
|
|
175
|
+
private modelLabel;
|
|
166
176
|
private updateSpeaker;
|
|
167
177
|
private promptGlyph;
|
|
168
178
|
private inputPanel;
|
|
@@ -80,13 +80,14 @@ class ChatApp {
|
|
|
80
80
|
session;
|
|
81
81
|
features;
|
|
82
82
|
banner;
|
|
83
|
+
minimal;
|
|
83
84
|
unsubscribe;
|
|
84
85
|
unsubscribeTheme;
|
|
85
86
|
unsubscribeSubAgents;
|
|
86
87
|
runUnsubs = new Set();
|
|
87
88
|
activeRuns = new Set();
|
|
88
89
|
mentionAc;
|
|
89
|
-
status = { label: 'ready', busy: false, spinnerTick: 0, context: ''
|
|
90
|
+
status = { label: 'ready', busy: false, spinnerTick: 0, context: '' };
|
|
90
91
|
running = false;
|
|
91
92
|
queue = [];
|
|
92
93
|
pendingShell = [];
|
|
@@ -116,6 +117,7 @@ class ChatApp {
|
|
|
116
117
|
this.session = host.session;
|
|
117
118
|
this.features = host.features ?? {};
|
|
118
119
|
this.banner = host.banner;
|
|
120
|
+
this.minimal = host.minimal ?? false;
|
|
119
121
|
this.transcript.thinkingVisible = host.initialThinking;
|
|
120
122
|
this.history = host.history?.load() ?? [];
|
|
121
123
|
this.historyIndex = this.history.length;
|
|
@@ -1054,15 +1056,24 @@ class ChatApp {
|
|
|
1054
1056
|
// backend may be unreachable; surfaced on first send
|
|
1055
1057
|
}
|
|
1056
1058
|
}
|
|
1057
|
-
/**
|
|
1058
|
-
|
|
1059
|
+
/** Styled model id + provider + active agent, shown in the input container footer. */
|
|
1060
|
+
modelLabel() {
|
|
1059
1061
|
const ref = this.host.modelRef();
|
|
1060
1062
|
const slash = ref.indexOf('/');
|
|
1061
1063
|
const id = slash >= 0 ? ref.slice(slash + 1) : ref;
|
|
1062
1064
|
const providerName = slash >= 0 ? ref.slice(0, slash) : '';
|
|
1063
1065
|
const model = this.models.find((m) => m.id === id);
|
|
1064
1066
|
const provider = model?.ownedBy ?? providerName;
|
|
1065
|
-
|
|
1067
|
+
const theme = this.theme();
|
|
1068
|
+
const bold = (0, theme_js_1.styleToAnsi)({ fg: theme.colors.text, bold: true });
|
|
1069
|
+
const dim = (0, theme_js_1.styleToAnsi)({ fg: theme.colors.textMuted });
|
|
1070
|
+
const head = provider ? `${bold}${id}${RESET} ${dim}${provider}${RESET}` : `${bold}${id}${RESET}`;
|
|
1071
|
+
const agent = this.host.agentRef();
|
|
1072
|
+
if (!agent)
|
|
1073
|
+
return head;
|
|
1074
|
+
const hex = (0, theme_js_1.asHexColor)(this.host.agentColor());
|
|
1075
|
+
const agentSgr = hex ? (0, theme_js_1.styleToAnsi)({ fg: hex, bold: true }) : dim;
|
|
1076
|
+
return `${head} ${dim}·${RESET} ${agentSgr}@${agent}${RESET}`;
|
|
1066
1077
|
}
|
|
1067
1078
|
updateSpeaker() {
|
|
1068
1079
|
this.transcript.speaker = { name: this.host.agentRef(), color: (0, theme_js_1.asHexColor)(this.host.agentColor()) };
|
|
@@ -1081,19 +1092,27 @@ class ChatApp {
|
|
|
1081
1092
|
}
|
|
1082
1093
|
inputPanel() {
|
|
1083
1094
|
const inner = this.approvalView() ?? this.editorInner();
|
|
1095
|
+
if (this.minimal)
|
|
1096
|
+
return (0, mu_tui_1.box)(inner, { padding: 0 });
|
|
1084
1097
|
return (0, mu_tui_1.box)(inner, { background: this.theme().colors.surface, padding: 1 });
|
|
1085
1098
|
}
|
|
1086
1099
|
editorInner() {
|
|
1087
1100
|
const prompt = this.promptGlyph();
|
|
1088
1101
|
const editor = this.editor;
|
|
1089
1102
|
const editorRows = editor.rows();
|
|
1103
|
+
const label = this.minimal ? '' : this.modelLabel();
|
|
1090
1104
|
return {
|
|
1091
1105
|
render: (s) => {
|
|
1092
1106
|
if (s.width <= 0 || s.height <= 0)
|
|
1093
1107
|
return;
|
|
1094
1108
|
s.text(0, 0, prompt);
|
|
1095
|
-
const
|
|
1109
|
+
const reserve = label ? 2 : 1;
|
|
1110
|
+
const rows = Math.min(editorRows, Math.max(1, s.height - reserve));
|
|
1096
1111
|
s.child(editor, { x: PROMPT_WIDTH, y: 0, width: Math.max(1, s.width - PROMPT_WIDTH), height: rows });
|
|
1112
|
+
if (label) {
|
|
1113
|
+
const labelRow = rows + 1;
|
|
1114
|
+
s.text(0, labelRow, (0, mu_tui_1.visibleWidth)(label) > s.width ? (0, mu_tui_1.truncateToWidth)(label, s.width) : label);
|
|
1115
|
+
}
|
|
1097
1116
|
},
|
|
1098
1117
|
};
|
|
1099
1118
|
}
|
|
@@ -1153,7 +1172,7 @@ class ChatApp {
|
|
|
1153
1172
|
return children;
|
|
1154
1173
|
}
|
|
1155
1174
|
statusBar() {
|
|
1156
|
-
this.status.
|
|
1175
|
+
this.status.minimal = this.minimal;
|
|
1157
1176
|
return (0, status_js_1.statusComponent)(this.status, this.theme());
|
|
1158
1177
|
}
|
|
1159
1178
|
dock() {
|
|
@@ -1282,19 +1301,17 @@ class ChatApp {
|
|
|
1282
1301
|
const focused = this.focusedSub();
|
|
1283
1302
|
const showBanner = this.banner !== undefined && this.transcript.entries.length === 0 && !focused;
|
|
1284
1303
|
const spacer = { render: () => { } };
|
|
1285
|
-
const inner = focused
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
// Conversation: transcript fills, input docked at the bottom.
|
|
1297
|
-
: (0, mu_tui_1.column)([(0, mu_tui_1.flex)(this.scroll), this.dock()]);
|
|
1304
|
+
const inner = focused ? this.subAgentView(focused) : showBanner
|
|
1305
|
+
// Splash: banner + a centered, width-limited minimal input; status pinned at the bottom.
|
|
1306
|
+
? (0, mu_tui_1.column)([
|
|
1307
|
+
(0, mu_tui_1.flex)(spacer),
|
|
1308
|
+
this.bannerBlock(),
|
|
1309
|
+
this.centered((0, mu_tui_1.column)(this.inputGroup()), SPLASH_INPUT_WIDTH),
|
|
1310
|
+
(0, mu_tui_1.flex)(spacer),
|
|
1311
|
+
this.statusBar(),
|
|
1312
|
+
])
|
|
1313
|
+
// Conversation: transcript fills, input docked at the bottom.
|
|
1314
|
+
: (0, mu_tui_1.column)([(0, mu_tui_1.flex)(this.scroll), this.dock()]);
|
|
1298
1315
|
return {
|
|
1299
1316
|
render: (s) => {
|
|
1300
1317
|
s.fill({ x: 0, y: 0, width: s.width, height: s.height }, this.theme().colors.background);
|
|
@@ -8,8 +8,8 @@ export interface StatusState {
|
|
|
8
8
|
busy: boolean;
|
|
9
9
|
spinnerTick: number;
|
|
10
10
|
context: string;
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/** Lean mode: hide the context readout, leaving only a busy spinner. */
|
|
12
|
+
minimal?: boolean;
|
|
13
13
|
}
|
|
14
14
|
export declare function statusFromEvent(event: AgentSessionEvent): string | undefined;
|
|
15
15
|
export declare function statusComponent(state: StatusState, theme: Theme): Component;
|
|
@@ -39,10 +39,8 @@ function statusComponent(state, theme) {
|
|
|
39
39
|
return;
|
|
40
40
|
const muted = (0, theme_js_1.styleToAnsi)(theme.styles.muted);
|
|
41
41
|
const spinner = `${muted}${(0, exports.spinnerFrame)(state.spinnerTick)}${RESET}`;
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
const left = [model, activity].filter(Boolean).join(`${muted} · ${RESET}`);
|
|
45
|
-
const right = state.context ? `${muted}${state.context}${RESET}` : '';
|
|
42
|
+
const left = state.busy ? (state.label ? `${spinner} ${muted}${state.label}${RESET}` : spinner) : '';
|
|
43
|
+
const right = state.minimal ? '' : (state.context ? `${muted}${state.context}${RESET}` : '');
|
|
46
44
|
if (!left && !right) {
|
|
47
45
|
s.text(0, 0, '');
|
|
48
46
|
return;
|