pi-better-harness 0.32.0 → 0.32.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.
|
@@ -87,7 +87,7 @@ export interface AgentCommandUi {
|
|
|
87
87
|
editor(title: string, prefill?: string): Promise<string | undefined>;
|
|
88
88
|
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
89
89
|
setWidget?(key: string, content: string[] | undefined): void;
|
|
90
|
-
custom?<T>(factory: (tui: { requestRender(): void }, theme: { fg(color: string, text: string): string }, keybindings: unknown, done: (value: T) => void) => unknown, options?: { overlay: boolean }): Promise<T>;
|
|
90
|
+
custom?<T>(factory: (tui: { requestRender(): void }, theme: { fg(color: string, text: string): string }, keybindings: unknown, done: (value: T) => void) => unknown, options?: { overlay: boolean; overlayOptions?: import("@earendil-works/pi-tui").OverlayOptions }): Promise<T>;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
export interface AgentCommandHost {
|
|
@@ -724,6 +724,7 @@ async function finishInspection(host: AgentCommandHost, commandResult: AgentComm
|
|
|
724
724
|
const entries = catalog ?? { entries: [view!], diagnostics: commandResult.diagnostics, revision: "", text: "" };
|
|
725
725
|
try {
|
|
726
726
|
host.ui.setWidget?.("agents-catalog", undefined);
|
|
727
|
+
let viewportHeight = 30;
|
|
727
728
|
await host.ui.custom<void>((tui, theme, _keys, done) =>
|
|
728
729
|
createCatalogComponent(entries, theme, () => tui.requestRender(), () => done(), view?.id, deps.sessionSettings ? {
|
|
729
730
|
get: () => presentCatalog(readCatalog(location(host, deps)), deps.enrich),
|
|
@@ -740,7 +741,13 @@ async function finishInspection(host: AgentCommandHost, commandResult: AgentComm
|
|
|
740
741
|
}
|
|
741
742
|
return { ok: saved.ok, message: saved.ok ? `Defaults saved (${saved.scope}): ${id}.` : saved.diagnostics.map((item) => item.message).join(" ") };
|
|
742
743
|
},
|
|
743
|
-
} : undefined
|
|
744
|
+
} : undefined, () => viewportHeight), {
|
|
745
|
+
overlay: true,
|
|
746
|
+
overlayOptions: {
|
|
747
|
+
anchor: "top-left", width: "100%", maxHeight: "100%", margin: 0,
|
|
748
|
+
visible: (_width, height) => { viewportHeight = height; return true; },
|
|
749
|
+
},
|
|
750
|
+
});
|
|
744
751
|
if (deps.sessionSettings) {
|
|
745
752
|
const current = readCatalog(location(host, deps));
|
|
746
753
|
const refreshed = view ? presentCatalogEntry(current, view.id, deps.enrich) : presentCatalog(current, deps.enrich);
|
|
@@ -34,16 +34,18 @@ function cell(value: string, width: number): string {
|
|
|
34
34
|
|
|
35
35
|
export function catalogColumns(entry: OperationView | null, width: number): string {
|
|
36
36
|
const available = Math.max(1, width - 2);
|
|
37
|
-
const sourceWidth = available >=
|
|
38
|
-
const stateWidth = available >=
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
37
|
+
const sourceWidth = available >= 82 ? 11 : 0;
|
|
38
|
+
const stateWidth = available >= 60 ? 10 : 0;
|
|
39
|
+
const effortWidth = available >= 32 ? 7 : 0;
|
|
40
|
+
const gaps = (sourceWidth ? 2 : 0) + (stateWidth ? 2 : 0) + (effortWidth ? 2 : 0) + 2;
|
|
41
|
+
const modelWidth = Math.min(Math.max(1, available - gaps - effortWidth - sourceWidth - stateWidth - 1), Math.min(28, Math.max(20, Math.floor(available * 0.3))));
|
|
42
|
+
const nameWidth = Math.max(1, available - modelWidth - effortWidth - sourceWidth - stateWidth - gaps);
|
|
42
43
|
const name = entry ? entry.identity.name || entry.id : "NAME";
|
|
43
44
|
const model = entry ? modelLabel(entry) : "MODEL";
|
|
44
45
|
const source = entry ? entry.identity.scope ?? "unknown" : "SOURCE";
|
|
45
46
|
const status = entry ? stateLabel(entry) : "STATE";
|
|
46
|
-
|
|
47
|
+
const effort = entry ? entry.actualEffort ?? entry.fields?.effort.value ?? "(unset)" : "EFFORT";
|
|
48
|
+
return [cell(name, nameWidth), cell(model, modelWidth), ...(effortWidth ? [cell(effort, effortWidth)] : []), ...(sourceWidth ? [cell(source, sourceWidth)] : []), ...(stateWidth ? [cell(status, stateWidth)] : [])].join(" ").trimEnd();
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
function wrapLine(text: string, width: number): string[] {
|
|
@@ -60,7 +62,7 @@ function wrapLine(text: string, width: number): string[] {
|
|
|
60
62
|
return rows;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
export function catalogDetailLines(entry: OperationView): string[] {
|
|
65
|
+
export function catalogDetailLines(entry: OperationView, editable = false): string[] {
|
|
64
66
|
const lines = [
|
|
65
67
|
`${entry.identity.name || entry.id} · ${entry.identity.kind ?? "unknown"}`,
|
|
66
68
|
entry.id,
|
|
@@ -73,8 +75,7 @@ export function catalogDetailLines(entry: OperationView): string[] {
|
|
|
73
75
|
"",
|
|
74
76
|
"Configuration",
|
|
75
77
|
...(entry.fields ? [
|
|
76
|
-
fieldLine("Model", entry.fields.model),
|
|
77
|
-
fieldLine("Effort", entry.fields.effort),
|
|
78
|
+
...(!editable ? [fieldLine("Model", entry.fields.model), fieldLine("Effort", entry.fields.effort)] : []),
|
|
78
79
|
fieldLine("Tier", entry.fields.tier),
|
|
79
80
|
] : []),
|
|
80
81
|
`Actual ${entry.actualModel ?? "not resolved"}${entry.actualEffort ? ` · ${entry.actualEffort}` : ""}`,
|
|
@@ -95,9 +96,10 @@ export function createCatalogComponent(
|
|
|
95
96
|
close: () => void,
|
|
96
97
|
initialId?: string,
|
|
97
98
|
editor?: CatalogEditor,
|
|
99
|
+
getHeight: () => number = () => 30,
|
|
98
100
|
) {
|
|
99
101
|
let selectedId = initialId ?? catalog.entries[0]?.id;
|
|
100
|
-
let mode: "list" | "detail" | "prompt"
|
|
102
|
+
let mode: "list" | "detail" | "prompt" = initialId ? "detail" : "list";
|
|
101
103
|
let query = "";
|
|
102
104
|
let scroll = 0;
|
|
103
105
|
let settingRow = 0;
|
|
@@ -159,7 +161,7 @@ export function createCatalogComponent(
|
|
|
159
161
|
const selected = () => filtered().find((entry) => entry.id === selectedId);
|
|
160
162
|
const detailRows = (entry: OperationView, width: number) => (mode === "prompt"
|
|
161
163
|
? [`${entry.identity.name || entry.id} · effective instructions`, "", ...(entry.instructions?.split("\n") ?? ["(none)"])]
|
|
162
|
-
: catalogDetailLines(entry))
|
|
164
|
+
: catalogDetailLines(entry, !!editor))
|
|
163
165
|
.flatMap((text) => text ? wrapLine(text, Math.max(1, width - 2)) : [""]);
|
|
164
166
|
let lastDetailWidth = 80;
|
|
165
167
|
|
|
@@ -168,21 +170,32 @@ export function createCatalogComponent(
|
|
|
168
170
|
set focused(value: boolean) { focused = value; search.focused = value && !!picker && pickerKey === "model"; },
|
|
169
171
|
render(width: number): string[] {
|
|
170
172
|
const safeWidth = Math.max(1, width);
|
|
173
|
+
const height = Math.max(1, Math.floor(getHeight()));
|
|
174
|
+
const footerRows = (editor && mode !== "prompt" ? 2 : 0) + (message ? 2 : 0);
|
|
175
|
+
const diagnosticRows = Math.min(catalog.diagnostics.length, 3);
|
|
176
|
+
const listSize = Math.max(1, height - 5 - footerRows - diagnosticRows - 1);
|
|
177
|
+
const controlRows = editor && mode === "detail" ? (safeWidth < 42 ? 5 : 3) : 0;
|
|
178
|
+
const detailSize = Math.max(1, height - 5 - footerRows - controlRows);
|
|
179
|
+
const fit = (lines: string[]) => {
|
|
180
|
+
const result = lines.slice(0, height).map(line);
|
|
181
|
+
while (result.length < height) result.push("");
|
|
182
|
+
return result;
|
|
183
|
+
};
|
|
171
184
|
lastDetailWidth = safeWidth;
|
|
172
185
|
const line = (text: string) => truncateToWidth(text, safeWidth);
|
|
173
186
|
const rows = filtered();
|
|
174
187
|
const current = selected();
|
|
175
188
|
const detailLines = current ? detailRows(current, safeWidth) : [];
|
|
176
189
|
const selectedIndex = rows.findIndex((entry) => entry.id === selectedId);
|
|
177
|
-
const start = Math.max(0, Math.min(selectedIndex -
|
|
178
|
-
|
|
190
|
+
const start = Math.max(0, Math.min(selectedIndex - Math.floor(listSize / 2), rows.length - listSize));
|
|
191
|
+
scroll = Math.max(0, Math.min(scroll, detailLines.length - detailSize));
|
|
192
|
+
if (picker) return fit([
|
|
179
193
|
theme.fg("accent", `Agents / ${pickerKey}`), "",
|
|
180
194
|
...(pickerKey === "model" ? search.render(safeWidth) : []),
|
|
181
195
|
...picker.render(safeWidth),
|
|
182
196
|
...(message ? [theme.fg(error ? "error" : "muted", message)] : []),
|
|
183
|
-
]
|
|
184
|
-
const
|
|
185
|
-
theme.fg("accent", `Agents / ${current.identity.name || current.id}`), "",
|
|
197
|
+
]);
|
|
198
|
+
const controls = editor && mode === "detail" && current ? [
|
|
186
199
|
...["Model", "Effort"].flatMap((label, index) => {
|
|
187
200
|
const key = index === 0 ? "model" : "effort";
|
|
188
201
|
const field = current.fields?.[key];
|
|
@@ -194,38 +207,41 @@ export function createCatalogComponent(
|
|
|
194
207
|
const text = `${settingRow === index ? "> " : " "}${label.padEnd(18)} ${value}`;
|
|
195
208
|
if (safeWidth < 42) {
|
|
196
209
|
const title = `${settingRow === index ? "> " : " "}${label}`;
|
|
197
|
-
return [settingRow === index ? highlight(title, safeWidth) : title,
|
|
210
|
+
return [settingRow === index ? highlight(title, safeWidth) : title, ` ${truncateToWidth(value, Math.max(1, safeWidth - 2))}`];
|
|
198
211
|
}
|
|
199
212
|
return [settingRow === index ? highlight(text, safeWidth) : text];
|
|
200
213
|
}),
|
|
201
|
-
] : mode !== "list" && current ? [
|
|
202
|
-
theme.fg("accent", mode === "prompt" ? "Agents / prompt" : "Agents / inspect"),
|
|
203
|
-
theme.fg("dim", mode === "prompt" ? "← details · ↑↓ scroll · Esc close" : `${editor ? "e settings · " : ""}p prompt · ← list · ↑↓ scroll · Esc close`),
|
|
204
214
|
"",
|
|
205
|
-
|
|
215
|
+
] : [];
|
|
216
|
+
const lines = mode !== "list" && current ? [
|
|
217
|
+
theme.fg("accent", mode === "prompt" ? "Agents / prompt" : editor ? `Agents / ${current.identity.name || current.id}` : "Agents / inspect"),
|
|
218
|
+
theme.fg("dim", mode === "prompt" ? "← details · ↑↓ scroll · Esc back" : `${editor ? "Enter change · " : ""}p prompt · ← list · ↑↓ ${editor ? "select / scroll" : "scroll"} · Esc back`),
|
|
219
|
+
"",
|
|
220
|
+
...controls,
|
|
221
|
+
...detailLines.slice(scroll, scroll + detailSize).map((text, i) =>
|
|
206
222
|
i === 0 && scroll === 0 ? theme.fg("accent", text) : text),
|
|
207
223
|
"",
|
|
208
|
-
theme.fg("dim", `Showing ${scroll + 1}-${Math.min(scroll +
|
|
224
|
+
theme.fg("dim", `Showing ${scroll + 1}-${Math.min(scroll + detailSize, detailLines.length)} of ${detailLines.length} lines`),
|
|
209
225
|
] : [
|
|
210
226
|
theme.fg("accent", `Agents · ${catalog.entries.length} definitions`),
|
|
211
227
|
theme.fg("dim", "Type to filter · ↑↓ select · Enter inspect · Esc close"),
|
|
212
228
|
`Search ${query || "(all roles and agents)"}`,
|
|
213
229
|
"",
|
|
214
230
|
theme.fg("dim", ` ${catalogColumns(null, safeWidth)}`),
|
|
215
|
-
...rows.slice(start, start +
|
|
231
|
+
...rows.slice(start, start + listSize).map((entry) => {
|
|
216
232
|
const active = selectedId === entry.id;
|
|
217
233
|
const marker = active ? "›" : " ";
|
|
218
234
|
const text = `${marker} ${catalogColumns(entry, safeWidth)}`;
|
|
219
235
|
return active ? highlight(text, safeWidth) : text;
|
|
220
236
|
}),
|
|
221
237
|
...(rows.length === 0 ? [" No matching definitions"] : []),
|
|
222
|
-
...(rows.length >
|
|
238
|
+
...(rows.length > listSize ? [theme.fg("dim", ` ${start + 1}-${Math.min(start + listSize, rows.length)} of ${rows.length}`)] : []),
|
|
223
239
|
...catalog.diagnostics.slice(0, 2).map((diagnostic) => theme.fg("warning", `! ${diagnostic.message}`)),
|
|
224
240
|
...(catalog.diagnostics.length > 2 ? [theme.fg("warning", `! ${catalog.diagnostics.length - 2} more catalog diagnostics`)] : []),
|
|
225
241
|
];
|
|
226
|
-
if (editor && mode !== "prompt") lines.push("", theme.fg("dim",
|
|
242
|
+
if (editor && mode !== "prompt") lines.push("", theme.fg("dim", "ctrl+s Save selected defaults"));
|
|
227
243
|
if (message) lines.push("", theme.fg(error ? "error" : "muted", message));
|
|
228
|
-
return lines
|
|
244
|
+
return fit(lines);
|
|
229
245
|
},
|
|
230
246
|
handleInput(data: string): void {
|
|
231
247
|
const rows = filtered();
|
|
@@ -239,25 +255,26 @@ export function createCatalogComponent(
|
|
|
239
255
|
}
|
|
240
256
|
if (editor && matchesKey(data, "ctrl+s")) { save(); return; }
|
|
241
257
|
if (matchesKey(data, "escape") || matchesKey(data, "left")) {
|
|
242
|
-
if (mode === "prompt"
|
|
258
|
+
if (mode === "prompt") { mode = "detail"; scroll = 0; }
|
|
243
259
|
else if (mode === "detail") { mode = "list"; scroll = 0; }
|
|
244
260
|
else if (query) { query = ""; selectedId = catalog.entries[0]?.id; }
|
|
245
261
|
else { close(); return; }
|
|
246
262
|
} else if (matchesKey(data, "up") || matchesKey(data, "down")) {
|
|
247
263
|
const delta = matchesKey(data, "down") ? 1 : -1;
|
|
248
|
-
if (mode === "
|
|
264
|
+
if (mode === "detail" && editor && scroll === 0 && (delta < 0 || settingRow === 0)) settingRow = Math.max(0, Math.min(1, settingRow + delta));
|
|
249
265
|
else if (mode !== "list" && selected()) scroll = Math.max(0, Math.min(detailRows(selected()!, lastDetailWidth).length - 1, scroll + delta));
|
|
250
266
|
else if (rows.length) selectedId = rows[Math.max(0, Math.min(rows.length - 1, rows.findIndex((entry) => entry.id === selectedId) + delta))]?.id;
|
|
251
267
|
} else if (matchesKey(data, "enter") && mode === "list" && selected()) {
|
|
252
268
|
mode = "detail";
|
|
269
|
+
settingRow = 0;
|
|
253
270
|
scroll = 0;
|
|
254
271
|
} else if ((data === "p" || data === "P") && mode === "detail" && selected()) {
|
|
255
272
|
mode = "prompt";
|
|
256
273
|
scroll = 0;
|
|
257
274
|
} else if (editor && (data === "e" || data === "E") && mode === "detail" && selected()) {
|
|
258
|
-
mode = "settings";
|
|
259
275
|
settingRow = 0;
|
|
260
|
-
|
|
276
|
+
scroll = 0;
|
|
277
|
+
} else if (editor && mode === "detail" && (matchesKey(data, "enter") || data === " ")) {
|
|
261
278
|
try { edit(); } catch (cause) { report(cause instanceof Error ? cause.message : String(cause), true); }
|
|
262
279
|
} else if (mode === "list" && matchesKey(data, "backspace")) {
|
|
263
280
|
query = query.slice(0, -1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-better-harness",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.1",
|
|
4
4
|
"description": "Pi extension bundle for a write sandbox, subagents, background tasks, SSH, goals, and structured plans.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"pi-better-plan": "0.9.1",
|
|
59
59
|
"pi-better-sandbox": "0.13.0",
|
|
60
60
|
"pi-better-ssh": "0.1.3",
|
|
61
|
-
"pi-better-subagents": "0.20.
|
|
61
|
+
"pi-better-subagents": "0.20.1",
|
|
62
62
|
"proper-lockfile": "^4.1.2",
|
|
63
63
|
"smol-toml": "1.9.0",
|
|
64
64
|
"yaml": "^2.9.1"
|