Skip to content

Commit 4aa5ca6

Browse files
authored
feat: improve agent support in interactive rli (#209)
This PR adapts and modifies the changes from Jason's jason/agents_tab branch. There are a number of additions to the interactive rli interface: - New Agents screens: Add agent list (with private/public tabs), detail, and create screens to the interactive rli TUI, accessible from the main menu via [a] shortcut - Unified agent column definitions: Extract shared getAgentColumns() in agentService and buildAgentTableColumns() in a new agentColumns.ts module, used by both the CLI rli agent list and all interactive agent pickers (agent list, devbox create, benchmark job create) for consistent column layout (NAME, SOURCE, VERSION, ID, CREATED) with terminal-width-adaptive sizing - Agent picker in devbox create: Add optional agent mount field to the devbox creation form with a tabbed private/public agent picker that merges results during search - Agent picker in benchmark job create: Wire up agent selection using the shared columns, with server-side search and correct totalCount propagation - ResourcePicker improvements: Add extraDeps and extraOverhead props for tab-switching contexts; add nextCursor support for opaque/merged pagination cursors; stabilize checkbox column rendering via ref to reduce flicker; fix cross-page multi-select by tracking item objects in a ref Map - CLI agent list cleanup: Remove local ColumnDef/computeColumnWidths() in favor of shared getAgentColumns(); fix scoped package version styling (lastIndexOf("@")) - Routing & navigation: Register agent-list, agent-detail, agent-create screens in Router with proper cleanup; add Agents to main menu and benchmark menu label updates ## Files changed src/services/agentService.ts Add AgentColumn, agentVersionText(), getAgentColumns(); fix totalCount to use response.total_count src/components/agentColumns.ts New — shared buildAgentTableColumns() UI-layer column builder src/commands/agent/list.ts Replace local column/width logic with shared getAgentColumns() src/screens/AgentListScreen.tsx New — agent list with private/public tabs src/screens/AgentDetailScreen.tsx New — agent detail with delete operation src/screens/AgentCreateScreen.tsx New — agent creation form (npm/pip/git/object sources) src/components/MainMenu.tsx Add Agents menu item with [a] shortcut src/components/BenchmarkMenu.tsx Rename menu labels for clarity src/router/Router.tsx Register 3 new agent screens src/store/navigationStore.tsx Add agent screen types src/screens/MenuScreen.tsx Route "agents" selection to agent-list src/components/DevboxCreatePage.tsx Add agent mount picker with merged private+public search src/screens/BenchmarkJobCreateScreen.tsx Switch agent picker to single-select, use shared columns, server-side search src/components/ResourcePicker.tsx Add extraDeps, extraOverhead, nextCursor; stabilize columns via ref; fix cross-page selection tracking src/hooks/useCursorPagination.ts Support opaque nextCursor in fetch results
1 parent 86cda21 commit 4aa5ca6

15 files changed

Lines changed: 1568 additions & 156 deletions

src/commands/agent/list.ts

Lines changed: 22 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
*/
44

55
import chalk from "chalk";
6-
import { listAgents, type Agent } from "../../services/agentService.js";
6+
import {
7+
listAgents,
8+
getAgentColumns,
9+
type Agent,
10+
} from "../../services/agentService.js";
711
import { output, outputError } from "../../utils/output.js";
8-
import { formatTimeAgo } from "../../utils/time.js";
912

1013
interface ListOptions {
1114
full?: boolean;
@@ -16,81 +19,16 @@ interface ListOptions {
1619
output?: string;
1720
}
1821

19-
interface ColumnDef {
20-
header: string;
21-
raw: (agent: Agent) => string;
22-
styled: (agent: Agent) => string;
23-
}
24-
25-
const columns: ColumnDef[] = [
26-
{
27-
header: "NAME",
28-
raw: (a) => a.name,
29-
styled(a) {
30-
return this.raw(a);
31-
},
32-
},
33-
{
34-
header: "SOURCE",
35-
raw: (a) => (a as any).source?.type || "-",
36-
styled(a) {
37-
return this.raw(a);
38-
},
39-
},
40-
{
41-
header: "VERSION",
42-
raw: (a) => {
43-
const pkg =
44-
(a as any).source?.npm?.package_name ||
45-
(a as any).source?.pip?.package_name;
46-
return pkg ? `${pkg}@${a.version}` : a.version;
47-
},
48-
styled(a) {
49-
const pkg =
50-
(a as any).source?.npm?.package_name ||
51-
(a as any).source?.pip?.package_name;
52-
return pkg ? chalk.dim(pkg + "@") + a.version : a.version;
53-
},
22+
/** Styling rules keyed by column key. Columns not listed render unstyled. */
23+
const columnStyle: Record<string, (raw: string) => string> = {
24+
id: (v) => chalk.dim(v),
25+
created: (v) => chalk.dim(v),
26+
version: (v) => {
27+
// Dim the "pkg@" prefix when present. Use lastIndexOf to skip scoped package @ (e.g. @scope/pkg@1.0)
28+
const at = v.lastIndexOf("@");
29+
return at > 0 ? chalk.dim(v.slice(0, at + 1)) + v.slice(at + 1) : v;
5430
},
55-
{
56-
header: "ID",
57-
raw: (a) => a.id,
58-
styled(a) {
59-
return chalk.dim(a.id);
60-
},
61-
},
62-
{
63-
header: "CREATED",
64-
raw: (a) => formatTimeAgo(a.create_time_ms),
65-
styled(a) {
66-
return chalk.dim(this.raw(a));
67-
},
68-
},
69-
];
70-
71-
function computeColumnWidths(agents: Agent[]): number[] {
72-
const minPad = 2;
73-
const maxPad = 4;
74-
const termWidth = process.stdout.columns || 120;
75-
76-
// Min width per column: max of header and all row values, plus minimum padding
77-
const minWidths = columns.map((col) => {
78-
const maxContent = agents.reduce(
79-
(w, a) => Math.max(w, col.raw(a).length),
80-
col.header.length,
81-
);
82-
return maxContent + minPad;
83-
});
84-
85-
const totalMin = minWidths.reduce((s, w) => s + w, 0);
86-
const slack = termWidth - totalMin;
87-
const extraPerCol = Math.min(
88-
maxPad - minPad,
89-
Math.max(0, Math.floor(slack / columns.length)),
90-
);
91-
92-
return minWidths.map((w) => w + extraPerCol);
93-
}
31+
};
9432

9533
function padStyled(raw: string, styled: string, width: number): string {
9634
return styled + " ".repeat(Math.max(0, width - raw.length));
@@ -105,18 +43,23 @@ export function printAgentTable(agents: Agent[]): void {
10543
return;
10644
}
10745

108-
const widths = computeColumnWidths(agents);
10946
const termWidth = process.stdout.columns || 120;
47+
const columns = getAgentColumns(agents, termWidth, false);
11048

11149
// Header
112-
const header = columns.map((col, i) => col.header.padEnd(widths[i])).join("");
50+
const header = columns.map((col) => col.label.padEnd(col.width)).join("");
11351
console.log(chalk.bold(header));
11452
console.log(chalk.dim("─".repeat(Math.min(header.length, termWidth))));
11553

11654
// Rows
11755
for (const agent of agents) {
11856
const line = columns
119-
.map((col, i) => padStyled(col.raw(agent), col.styled(agent), widths[i]))
57+
.map((col) => {
58+
const raw = col.getValue(agent);
59+
const styleFn = columnStyle[col.key];
60+
const styled = styleFn ? styleFn(raw) : raw;
61+
return padStyled(raw, styled, col.width);
62+
})
12063
.join("");
12164
console.log(line);
12265
}

src/components/BenchmarkMenu.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,21 @@ interface BenchmarkMenuItem {
6767
const benchmarkMenuItems: BenchmarkMenuItem[] = [
6868
{
6969
key: "benchmarks",
70-
label: "Benchmark Defs",
70+
label: "Available Benchmarks",
7171
description: "View benchmark definitions",
7272
icon: "◉",
7373
color: colors.primary,
7474
},
7575
{
7676
key: "benchmark-jobs",
77-
label: "Orchestrator Jobs",
77+
label: "Benchmark Orchestrator Jobs",
7878
description: "Run and manage benchmark jobs",
7979
icon: "▲",
8080
color: colors.warning,
8181
},
8282
{
8383
key: "benchmark-runs",
84-
label: "Legacy Runs",
84+
label: "Manual Benchmark Runs",
8585
description: "View and manage benchmark executions",
8686
icon: "◇",
8787
color: colors.success,

0 commit comments

Comments
 (0)