Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 53 additions & 9 deletions src/cli/commands/ps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,14 @@ const PS_DROP_ORDER: readonly PsColumnKey[] = [
"status",
];

const PS_GROW_ORDER: readonly PsColumnKey[] = [
"model",
"name",
"agent",
"status",
"lastEvent",
];

function columnValue(col: PsColumnKey, s: PsSession): string {
switch (col) {
case "id":
Expand Down Expand Up @@ -306,25 +314,61 @@ export function formatPsJson(sessions: readonly PsSession[]): string {
export function renderPsTable(sessions: readonly PsSession[], width?: number): string {
const maxWidth = resolvePsWidth(width);
const { cols, widths } = planPsColumns(maxWidth);
const rowCells = sessions.map((s) => cols.map((c) => columnValue(c.key, s)));
const contentWidths = new Map<PsColumnKey, number>();

// CWD is fill: with rows it takes the longest path that still fits, without
// rows it shrinks to its header, so the divider matches the real width.
if (cols.some((c) => c.key === "cwd")) {
const cwdContent = Math.max(
3,
...sessions.map((s) => visibleWidth(columnValue("cwd", s))),
for (const [index, col] of cols.entries()) {
const measured = Math.max(
visibleWidth(col.header),
...rowCells.map((cells) => visibleWidth(cells[index] || "")),
);
contentWidths.set(
col.key,
Number.isFinite(maxWidth) ? Math.max(col.pref, measured) : measured,
);
}

const naturalTableWidth =
cols.reduce((sum, col) => sum + (contentWidths.get(col.key) ?? col.pref), 0) +
SEP.length * Math.max(0, cols.length - 1);

// Keep complete values whenever the current column set fits. If one long
// value prevents that, spend the remaining width on the useful text columns
// before CWD gets its fill space.
if (Number.isFinite(maxWidth) && naturalTableWidth <= Math.floor(maxWidth)) {
for (const col of cols) {
widths.set(col.key, contentWidths.get(col.key) ?? col.pref);
}
} else if (Number.isFinite(maxWidth)) {
let available = Math.max(
0,
Math.floor(maxWidth) -
(cols.reduce((sum, col) => sum + (widths.get(col.key) ?? col.pref), 0) +
SEP.length * Math.max(0, cols.length - 1)),
);
for (const key of PS_GROW_ORDER) {
const current = widths.get(key);
const target = contentWidths.get(key);
if (current == null || target == null || target <= current || available <= 0) continue;
const increase = Math.min(target - current, available);
widths.set(key, current + increase);
available -= increase;
}
}

// CWD takes the remaining terminal width when stdout has a known size.
if (cols.some((c) => c.key === "cwd")) {
if (Number.isFinite(maxWidth)) {
const others = cols.filter((c) => c.key !== "cwd");
const othersWidth =
others.reduce((sum, c) => sum + (widths.get(c.key) ?? c.pref), 0) +
SEP.length * Math.max(0, cols.length - 1);
widths.set(
"cwd",
Math.max(3, Math.min(cwdContent, Math.floor(maxWidth) - othersWidth)),
Math.max(3, Math.floor(maxWidth) - othersWidth),
);
} else {
widths.set("cwd", cwdContent);
widths.set("cwd", contentWidths.get("cwd") ?? 3);
}
}

Expand All @@ -336,7 +380,7 @@ export function renderPsTable(sessions: readonly PsSession[], width?: number): s
};

const header = formatRow(cols.map((c) => c.header));
const rows = sessions.map((s) => formatRow(cols.map((c) => columnValue(c.key, s))));
const rows = rowCells.map(formatRow);
const tableWidth = Math.max(visibleWidth(header), ...rows.map((r) => visibleWidth(r)));
const dividerWidth = Number.isFinite(maxWidth)
? Math.min(tableWidth, Math.floor(maxWidth))
Expand Down
14 changes: 12 additions & 2 deletions tests/ps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,22 @@ describe("ps responsive table", () => {
});

it("truncates a long NAME with an ellipsis", () => {
const row = renderPsTable([wideSession()], 200).split("\n")[2];
const row = renderPsTable([wideSession()], 120).split("\n")[2];

expect(row).toContain("tighten-shim-pe…");
expect(row).toContain("tighten-shim-per-extra-l…");
expect(row).not.toContain("tighten-shim-per-extra-long");
});

it("uses available width to show the full model identifier", () => {
const model = "alibaba-token-plan/qwen3.8-max";
const row = renderPsTable(
[wideSession({ name: "worker", lastEvent: "x".repeat(100), cwd: "~/repo", model })],
160,
).split("\n")[2];

expect(row).toContain(model);
});

it("keeps the tail of a truncated CWD", () => {
const row = renderPsTable([wideSession()], 120).split("\n")[2];

Expand Down
Loading