Revert "style: OpenCode Todo-style right agent panel (bg fill, checklist, pinned footer)"
This reverts commit e0f08097ec.
This commit is contained in:
@@ -2,16 +2,6 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Restyled the persistent right-side agent panel (`/agents-panel`) to match
|
||||
OpenCode's Todo/plan sidebar: a full-height background column (no box border,
|
||||
panel background painted across the whole overlay width) with a bold `▼ agents`
|
||||
title, a flat OpenCode-style checklist (`[•]` in-progress in warning color,
|
||||
`[ ]` pending and `[✓]` completed in muted, `[✖]` failed in error), live
|
||||
per-agent activity lines under running agents, and the footer hint pinned to
|
||||
the bottom of the column. The panel now sits flush against the right edge of
|
||||
the terminal and stops two rows above the bottom so the editor and status bar
|
||||
stay visible.
|
||||
|
||||
- Fixed the research loop and agent tree getting permanently stuck after
|
||||
`research_control stop`: `start` now restarts a stopped/completed run in the
|
||||
same session (`AgentTree.restart` revives the run, archives old branches, and
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Theme } from "@earendil-works/pi-coding-agent";
|
||||
import { createAgentPanelComponent, agentLastActivity } from "./src/agent-tree-ui.js";
|
||||
import type { AgentRecord } from "./src/types.js";
|
||||
|
||||
const theme = new Theme(
|
||||
{ accent: "#8abeb7", border: "#5f87ff", borderAccent: "#00d7ff", borderMuted: "#505050", success: "#b5bd68", error: "#cc6666", warning: "#ffff00", muted: "#808080", dim: "#666666", text: "#d4d4d4", thinkingText: "#808080", userMessageText: "#d4d4d4", customMessageText: "#d4d4d4", customMessageLabel: "#9575cd", toolTitle: "#d4d4d4", toolOutput: "#808080", mdHeading: "#f0c674", mdLink: "#81a2be", mdLinkUrl: "#666666", mdCode: "#8abeb7", mdCodeBlock: "#b5bd68", mdCodeBlockBorder: "#808080", mdQuote: "#808080", mdQuoteBorder: "#808080", mdHr: "#808080", mdListBullet: "#8abeb7", toolDiffAdded: "#b5bd68", toolDiffRemoved: "#cc6666", toolDiffContext: "#808080", syntaxComment: "#808080", syntaxKeyword: "#cc6666", syntaxFunction: "#8abeb7", syntaxVariable: "#d4d4d4", syntaxString: "#b5bd68", syntaxNumber: "#f0c674", syntaxType: "#5f87ff", syntaxOperator: "#d4d4d4", syntaxPunctuation: "#808080", thinkingOff: "#666666", thinkingMinimal: "#666666", thinkingLow: "#666666", thinkingMedium: "#666666", thinkingHigh: "#666666", thinkingXhigh: "#666666", thinkingMax: "#666666", bashMode: "#cc6666" },
|
||||
{ selectedBg: "#3a3a4a", userMessageBg: "#343541", customMessageBg: "#2d2838", toolPendingBg: "#282832", toolSuccessBg: "#283228", toolErrorBg: "#3c2828" },
|
||||
"truecolor",
|
||||
);
|
||||
|
||||
const base: Omit<AgentRecord, "id" | "status" | "startedAt" | "finishedAt"> = {
|
||||
runId: "run-132965dd", parentId: "root", children: [], lineage: [], depth: 1,
|
||||
task: "x", taskFingerprint: "x", expectedOutput: "", completionCriteria: "", specPath: "",
|
||||
createdAt: new Date(Date.now() - 20 * 60000).toISOString(),
|
||||
};
|
||||
const mk = (id: string, status: AgentRecord["status"], startedMin: number, finishedMin?: number): AgentRecord => ({
|
||||
...base, id, status,
|
||||
startedAt: new Date(Date.now() - startedMin * 60000).toISOString(),
|
||||
finishedAt: finishedMin !== undefined ? new Date(Date.now() - finishedMin * 60000).toISOString() : undefined,
|
||||
});
|
||||
|
||||
// Fake tree
|
||||
const agents = new Map<string, AgentRecord>();
|
||||
agents.set("root", { ...base, id: "root", parentId: null, depth: 0, status: "completed", startedAt: new Date(Date.now() - 30 * 60000).toISOString(), finishedAt: new Date(Date.now() - 25 * 60000).toISOString() });
|
||||
agents.set("market-competitors-bb389dda", mk("market-competitors-bb389dda", "running", 3));
|
||||
agents.set("regulatory-gov-support-3b922f86", mk("regulatory-gov-support-3b922f86", "waiting", 3));
|
||||
agents.set("llm-infrastructure-niches-b1308f72", mk("llm-infrastructure-niches-b1308f72", "created", 0));
|
||||
agents.set("science-education-niches-293d399a", mk("science-education-niches-293d399a", "completed", 55, 12));
|
||||
agents.set("demand-b2b-b2g-57637d25", mk("demand-b2b-b2g-57637d25", "failed", 40, 9));
|
||||
const tree = {
|
||||
rootId: "root", runId: "run-132965dd",
|
||||
list: () => [...agents.values()],
|
||||
} as never;
|
||||
|
||||
const tui = { requestRender() {}, terminal: { rows: 40 } } as never;
|
||||
const panel = createAgentPanelComponent(tree, tui, theme, () => {});
|
||||
const out = panel.render(50);
|
||||
// strip ANSI, show visibly
|
||||
const strip = (s: string) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
||||
console.log("+" + "-".repeat(50) + "+");
|
||||
for (const line of out) console.log("|" + strip(line) + "|");
|
||||
console.log("+" + "-".repeat(50) + "+");
|
||||
console.log("lines:", out.length, "| last line ends with reset:", out[out.length-1]!.endsWith("\x1b[0m"));
|
||||
+13
-57
@@ -85,76 +85,32 @@ export function agentLastActivity(sessionFile: string | undefined): string | und
|
||||
return line || undefined;
|
||||
}
|
||||
|
||||
const PANEL_BG = "toolPendingBg";
|
||||
|
||||
/**
|
||||
* OpenCode-style persistent right-side panel: a full-height background column
|
||||
* (no box border) holding a flat checklist of agents, mirroring the Todo/plan
|
||||
* sidebar of OpenCode's TUI: `[•]` in-progress (warning), `[ ]` pending and
|
||||
* `[✓]` completed (muted), with a bold title and a footer pinned to the bottom.
|
||||
*/
|
||||
/** Persistent non-capturing right-side panel: live agent tree without keyboard navigation. */
|
||||
export function createAgentPanelComponent(
|
||||
tree: AgentTree,
|
||||
tui: { requestRender(): void; terminal: { rows: number } },
|
||||
tui: { requestRender(): void },
|
||||
theme: Theme,
|
||||
done: () => void,
|
||||
): { render(width: number): string[]; invalidate(): void; handleInput(data: string): void; dispose(): void } {
|
||||
const timer = setInterval(() => tui.requestRender(), 1500);
|
||||
/** Paint a line with the panel background, padded with spaces to the full overlay width (padding must be inside the bg ANSI codes, otherwise the TUI compositor resets it). */
|
||||
const paint = (line: string, width: number): string => theme.bg(PANEL_BG, truncateToWidth(line, width, "…", true));
|
||||
const blank = (width: number): string => theme.bg(PANEL_BG, " ".repeat(width));
|
||||
const marker = (record: AgentRecord): { glyph: string; color: (s: string) => string } => {
|
||||
switch (record.status) {
|
||||
case "running": return { glyph: "•", color: (s) => theme.fg("warning", s) };
|
||||
case "waiting": return { glyph: " ", color: (s) => theme.fg("muted", s) };
|
||||
case "completed": return { glyph: "✓", color: (s) => theme.fg("muted", s) };
|
||||
case "failed": return { glyph: "✖", color: (s) => theme.fg("error", s) };
|
||||
case "interrupted": return { glyph: "!", color: (s) => theme.fg("warning", s) };
|
||||
default: return { glyph: " ", color: (s) => theme.fg("dim", s) };
|
||||
}
|
||||
};
|
||||
const row = (record: AgentRecord, width: number, now: number): string => {
|
||||
const { glyph, color } = marker(record);
|
||||
const time = elapsed(record, now);
|
||||
return paint(`${color(`[${glyph}]`)} ${color(shortName(record.id))}${time ? ` ${theme.fg("dim", time)}` : ""}`, width);
|
||||
};
|
||||
return {
|
||||
render(width) {
|
||||
const rows = buildRows(tree).filter((row) => row.depth > 0);
|
||||
const now = Date.now();
|
||||
const all = tree.list().filter((agent) => agent.id !== tree.rootId);
|
||||
const isActive = (agent: AgentRecord): boolean => agent.status === "running" || agent.status === "waiting" || agent.status === "created";
|
||||
const active = all.filter(isActive);
|
||||
const finished = all.filter((agent) => !isActive(agent)).sort((a, b) => (b.finishedAt ?? "").localeCompare(a.finishedAt ?? ""));
|
||||
const visibleActive = active.slice(0, 8);
|
||||
const visibleFinished = finished.slice(0, 3);
|
||||
const hidden = Math.max(0, active.length - visibleActive.length + finished.length - visibleFinished.length);
|
||||
|
||||
const lines: string[] = [
|
||||
paint(theme.fg("text", theme.bold("▼ agents")) + theme.fg("dim", ` · ${tree.runId}`), width),
|
||||
paint(theme.fg("dim", `${active.length} active · ${all.length} agents`), width),
|
||||
blank(width),
|
||||
];
|
||||
for (const record of visibleActive) {
|
||||
lines.push(row(record, width, now));
|
||||
const active = rows.filter((row) => row.record.status === "running" || row.record.status === "waiting").length;
|
||||
const lines: string[] = [theme.fg("accent", `◆ agents`) + theme.fg("dim", ` · ${tree.runId}`), theme.fg("dim", `${active} active · ${rows.length} agents`), ""];
|
||||
for (const { record, prefix } of rows) {
|
||||
const icon = STATUS_ICON[record.status];
|
||||
const status = STATUS_COLOR[record.status](theme, record.status);
|
||||
const time = elapsed(record, now);
|
||||
lines.push(`${prefix}${theme.fg("text", icon)} ${theme.fg("text", shortName(record.id))} ${status}${time ? ` ${theme.fg("dim", time)}` : ""}`);
|
||||
if (record.status === "running" || record.status === "waiting") {
|
||||
const activity = agentLastActivity(record.sessionFile);
|
||||
if (activity) lines.push(paint(theme.fg("dim", ` ${truncateToWidth(activity, Math.max(20, width - 6))}`), width));
|
||||
if (activity) lines.push(theme.fg("dim", ` ${truncateToWidth(activity, Math.max(20, width - 8))}`));
|
||||
}
|
||||
}
|
||||
if (visibleFinished.length > 0) {
|
||||
lines.push(blank(width));
|
||||
for (const record of visibleFinished) lines.push(row(record, width, now));
|
||||
}
|
||||
if (hidden > 0) lines.push(paint(theme.fg("dim", `+${hidden} more…`), width));
|
||||
|
||||
// Fill the rest of the right column with the panel background, keeping the
|
||||
// editor + status rows at the bottom visible, and pin the footer at the bottom.
|
||||
const total = Math.max(1, tui.terminal.rows - 2);
|
||||
if (lines.length < total) {
|
||||
while (lines.length < total - 1) lines.push(blank(width));
|
||||
lines.push(paint(theme.fg("dim", "/agents — details · /agents-panel — hide"), width));
|
||||
}
|
||||
return lines;
|
||||
lines.push("", theme.fg("dim", "/agents — details · /agents-panel — hide"));
|
||||
return lines.map((line) => truncateToWidth(line, width));
|
||||
},
|
||||
invalidate() { /* rebuilt fresh on every render */ },
|
||||
handleInput() { /* non-capturing: keys go to the editor */ },
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ export class SupervisorIntegration {
|
||||
return createAgentPanelComponent(tree, tui, theme, () => { this.agentPanelClose = undefined; done(undefined); });
|
||||
}, {
|
||||
overlay: true,
|
||||
overlayOptions: { width: "40%", minWidth: 44, maxHeight: "100%", anchor: "top-right", margin: { top: 0, right: 0, bottom: 0, left: 1 }, nonCapturing: true, visible: (termWidth) => termWidth >= 100 },
|
||||
overlayOptions: { width: "40%", minWidth: 44, maxHeight: "100%", anchor: "top-right", margin: 1, nonCapturing: true, visible: (termWidth) => termWidth >= 100 },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user