chore: remove the /agents interactive overlay; keep the live widget
This commit is contained in:
+3
-7
@@ -31,18 +31,14 @@
|
||||
tree as paused, and resume requires the loop to actually be paused.
|
||||
- `read_artifact` now resolves symlinks before the confinement check, so a
|
||||
symlink inside the project cannot leak files from outside it.
|
||||
- Added a live subagent dashboard: a TUI widget above the editor shows the
|
||||
research run id, loop status, iteration, active agents with elapsed time,
|
||||
and recently finished agents (updated every 1.5 s, hidden when idle).
|
||||
- The live widget above the editor shows the research run id, loop status,
|
||||
iteration, active agents with elapsed time, and recently finished agents
|
||||
(updated every 1.5 s, hidden when idle).
|
||||
- Subagents no longer stall the main chat on serial model backends: new
|
||||
`agent_concurrency` (default 3) caps how many subagent sessions stream at
|
||||
once, and optional `subagent_model` routes subagents to a different
|
||||
model/backend (e.g. a local Ollama model) so they never contend with the
|
||||
main session.
|
||||
- New `/agents` command opens an interactive overlay with the full agent tree:
|
||||
tree glyphs (├─/└─/│), colored statuses and icons, live elapsed time,
|
||||
keyboard navigation (↑/↓), and a detail pane (Enter) showing each agent's
|
||||
task and result. The live widget header now hints at `/agents`.
|
||||
|
||||
## 0.1.1 — 2026-07-31
|
||||
|
||||
|
||||
graphify-out/cache/ast/v0.8.44/8b8f8ebbd94cd9c50f0597f06a6cd68d53e6605d682f260a087c9fc7176f7a2b.json
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "home_emil_desktop_hypothesis_machine_changelog_md", "label": "CHANGELOG.md", "file_type": "document", "source_file": "CHANGELOG.md", "source_location": "L1"}, {"id": "hypothesis_machine_changelog_changelog", "label": "Changelog", "file_type": "document", "source_file": "CHANGELOG.md", "source_location": "L1"}, {"id": "hypothesis_machine_changelog_unreleased", "label": "Unreleased", "file_type": "document", "source_file": "CHANGELOG.md", "source_location": "L3"}, {"id": "hypothesis_machine_changelog_0_1_1_2026_07_31", "label": "0.1.1 \u2014 2026-07-31", "file_type": "document", "source_file": "CHANGELOG.md", "source_location": "L47"}, {"id": "hypothesis_machine_changelog_0_1_0_2026_07_31", "label": "0.1.0 \u2014 2026-07-31", "file_type": "document", "source_file": "CHANGELOG.md", "source_location": "L60"}], "edges": [{"source": "home_emil_desktop_hypothesis_machine_changelog_md", "target": "hypothesis_machine_changelog_changelog", "relation": "contains", "confidence": "EXTRACTED", "source_file": "CHANGELOG.md", "source_location": "L1", "weight": 1.0}, {"source": "hypothesis_machine_changelog_changelog", "target": "hypothesis_machine_changelog_unreleased", "relation": "contains", "confidence": "EXTRACTED", "source_file": "CHANGELOG.md", "source_location": "L3", "weight": 1.0}, {"source": "hypothesis_machine_changelog_changelog", "target": "hypothesis_machine_changelog_0_1_1_2026_07_31", "relation": "contains", "confidence": "EXTRACTED", "source_file": "CHANGELOG.md", "source_location": "L47", "weight": 1.0}, {"source": "hypothesis_machine_changelog_changelog", "target": "hypothesis_machine_changelog_0_1_0_2026_07_31", "relation": "contains", "confidence": "EXTRACTED", "source_file": "CHANGELOG.md", "source_location": "L60", "weight": 1.0}], "input_tokens": 0, "output_tokens": 0}
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"/home/emil/Desktop/hypothesis-machine/package.json":{"size":1331,"mtime_ns":1785526465209535381,"hash":"f7514b7033a121f12b8960b05cad5a29bb4636b9641740561adc3b75352f95cb"},"/home/emil/Desktop/hypothesis-machine/tsconfig.build.json":{"size":171,"mtime_ns":1785523498961599333,"hash":"25868e1e77377761e5f756724739440927cdecf3d48c2800816407ef81b79222"},"/home/emil/Desktop/hypothesis-machine/tsconfig.json":{"size":430,"mtime_ns":1785523498961599333,"hash":"49863ef1ecd0b134189964c301f95090bf082241233c16055decd9aca6a9a314"},"/home/emil/Desktop/hypothesis-machine/.github/pull_request_template.md":{"size":534,"mtime_ns":1785526202969401851,"hash":"d76aeee1b42b19491e04604e657c0115b9b34df747f02b126e162df16538df1a"},"/home/emil/Desktop/hypothesis-machine/CHANGELOG.md":{"size":4559,"mtime_ns":1785531669243473488,"hash":"769d7b6bd162d14987cbe61d8f4563221c8450f44f201224c92c874efb2081c6"},"/home/emil/Desktop/hypothesis-machine/CONTRIBUTING.md":{"size":1522,"mtime_ns":1785526465210535393,"hash":"991a374ba1a32e74466f14e615736dec364ef2e22af71d3721a13e9f7779fd2c"},"/home/emil/Desktop/hypothesis-machine/README.md":{"size":6441,"mtime_ns":1785526465209535381,"hash":"d0ba5495074caf139af024082008ec136d4609995f58d431d716d24953bd7c07"},"/home/emil/Desktop/hypothesis-machine/SECURITY.md":{"size":974,"mtime_ns":1785526202966401815,"hash":"7870d4b34102270e45e1453c6052b25af09f1fe13bfdc4f244108b66a1e9a34b"},"/home/emil/Desktop/hypothesis-machine/docs/architecture.md":{"size":2002,"mtime_ns":1785524693984370854,"hash":"c04eb13e5a1728dc27f72e94bf88c179d26719c684e7f5f6129222910ae795cf"},"/home/emil/Desktop/hypothesis-machine/docs/development.md":{"size":1283,"mtime_ns":1785526465211535405,"hash":"f4af7af03f18576be7dbfec448e1443911d7fe43eabe9dc8ea0240052ff3383e"},"/home/emil/Desktop/hypothesis-machine/docs/pi-capabilities.md":{"size":4716,"mtime_ns":1785525665668981602,"hash":"32246de16c8fb36b826cb70b489f91ee0b3e5073bcbde729853ca04813fda9e4"},"/home/emil/Desktop/hypothesis-machine/docs/releasing.md":{"size":1269,"mtime_ns":1785526465213535429,"hash":"902bf6e8f541fbce500197fb303fb0ccfa2571998ee5bc57d7227e0f8d059b5a"},"/home/emil/Desktop/hypothesis-machine/docs/security.md":{"size":2817,"mtime_ns":1785526465213535429,"hash":"b8e428dd89560c6487d0177ae3f0b168dec24ef7727ad959e06f58dff206c5a1"},"/home/emil/Desktop/hypothesis-machine/prompts/agent-base.md":{"size":309,"mtime_ns":1785524192916593472,"hash":"6290b9307d35d28616e10d0043fd69150fac94ec4489b7a27a7fa1bbe830edf0"},"/home/emil/Desktop/hypothesis-machine/prompts/retrospective.md":{"size":244,"mtime_ns":1785524192917593484,"hash":"e16601b436e781f625c271fe5637d73c23a61780d3022ba0368503fc3b0516a9"},"/home/emil/Desktop/hypothesis-machine/prompts/supervisor.md":{"size":326,"mtime_ns":1785524192916593472,"hash":"78358e240ac6321433f93b14f138b6de917bba392078cb2e831f0276721968f3"},"/home/emil/Desktop/hypothesis-machine/prompts/synthesis.md":{"size":203,"mtime_ns":1785524192916593472,"hash":"33a91ff7d9ddc51110b59f673dea6bf954c48a6fc880f9a06b713dacb6c2c109"},"/home/emil/Desktop/hypothesis-machine/services/browser-worker/README.md":{"size":945,"mtime_ns":1785524192915593461,"hash":"eac3a08976ae79cb79300181be5b1b6cb8d0dd4a27aa8940e4e473a8429a15b8"}}
|
||||
{"/home/emil/Desktop/hypothesis-machine/package.json":{"size":1331,"mtime_ns":1785526465209535381,"hash":"f7514b7033a121f12b8960b05cad5a29bb4636b9641740561adc3b75352f95cb"},"/home/emil/Desktop/hypothesis-machine/tsconfig.build.json":{"size":171,"mtime_ns":1785523498961599333,"hash":"25868e1e77377761e5f756724739440927cdecf3d48c2800816407ef81b79222"},"/home/emil/Desktop/hypothesis-machine/tsconfig.json":{"size":430,"mtime_ns":1785523498961599333,"hash":"49863ef1ecd0b134189964c301f95090bf082241233c16055decd9aca6a9a314"},"/home/emil/Desktop/hypothesis-machine/.github/pull_request_template.md":{"size":534,"mtime_ns":1785526202969401851,"hash":"d76aeee1b42b19491e04604e657c0115b9b34df747f02b126e162df16538df1a"},"/home/emil/Desktop/hypothesis-machine/CHANGELOG.md":{"size":3998,"mtime_ns":1785531774587687095,"hash":"8b8f8ebbd94cd9c50f0597f06a6cd68d53e6605d682f260a087c9fc7176f7a2b"},"/home/emil/Desktop/hypothesis-machine/CONTRIBUTING.md":{"size":1522,"mtime_ns":1785526465210535393,"hash":"991a374ba1a32e74466f14e615736dec364ef2e22af71d3721a13e9f7779fd2c"},"/home/emil/Desktop/hypothesis-machine/README.md":{"size":6441,"mtime_ns":1785526465209535381,"hash":"d0ba5495074caf139af024082008ec136d4609995f58d431d716d24953bd7c07"},"/home/emil/Desktop/hypothesis-machine/SECURITY.md":{"size":974,"mtime_ns":1785526202966401815,"hash":"7870d4b34102270e45e1453c6052b25af09f1fe13bfdc4f244108b66a1e9a34b"},"/home/emil/Desktop/hypothesis-machine/docs/architecture.md":{"size":2002,"mtime_ns":1785524693984370854,"hash":"c04eb13e5a1728dc27f72e94bf88c179d26719c684e7f5f6129222910ae795cf"},"/home/emil/Desktop/hypothesis-machine/docs/development.md":{"size":1283,"mtime_ns":1785526465211535405,"hash":"f4af7af03f18576be7dbfec448e1443911d7fe43eabe9dc8ea0240052ff3383e"},"/home/emil/Desktop/hypothesis-machine/docs/pi-capabilities.md":{"size":4716,"mtime_ns":1785525665668981602,"hash":"32246de16c8fb36b826cb70b489f91ee0b3e5073bcbde729853ca04813fda9e4"},"/home/emil/Desktop/hypothesis-machine/docs/releasing.md":{"size":1269,"mtime_ns":1785526465213535429,"hash":"902bf6e8f541fbce500197fb303fb0ccfa2571998ee5bc57d7227e0f8d059b5a"},"/home/emil/Desktop/hypothesis-machine/docs/security.md":{"size":2817,"mtime_ns":1785526465213535429,"hash":"b8e428dd89560c6487d0177ae3f0b168dec24ef7727ad959e06f58dff206c5a1"},"/home/emil/Desktop/hypothesis-machine/prompts/agent-base.md":{"size":309,"mtime_ns":1785524192916593472,"hash":"6290b9307d35d28616e10d0043fd69150fac94ec4489b7a27a7fa1bbe830edf0"},"/home/emil/Desktop/hypothesis-machine/prompts/retrospective.md":{"size":244,"mtime_ns":1785524192917593484,"hash":"e16601b436e781f625c271fe5637d73c23a61780d3022ba0368503fc3b0516a9"},"/home/emil/Desktop/hypothesis-machine/prompts/supervisor.md":{"size":326,"mtime_ns":1785524192916593472,"hash":"78358e240ac6321433f93b14f138b6de917bba392078cb2e831f0276721968f3"},"/home/emil/Desktop/hypothesis-machine/prompts/synthesis.md":{"size":203,"mtime_ns":1785524192916593472,"hash":"33a91ff7d9ddc51110b59f673dea6bf954c48a6fc880f9a06b713dacb6c2c109"},"/home/emil/Desktop/hypothesis-machine/services/browser-worker/README.md":{"size":945,"mtime_ns":1785524192915593461,"hash":"eac3a08976ae79cb79300181be5b1b6cb8d0dd4a27aa8940e4e473a8429a15b8"}}
|
||||
@@ -1,90 +0,0 @@
|
||||
import { Key, matchesKey, truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
||||
import type { Theme } from "@earendil-works/pi-coding-agent";
|
||||
import type { AgentTree } from "./agent-tree.js";
|
||||
import type { AgentRecord } from "./types.js";
|
||||
|
||||
interface TreeRow { record: AgentRecord; prefix: string; depth: number }
|
||||
|
||||
/** Flatten the agent tree into rows with tree glyphs (├─/└─/│). */
|
||||
function buildRows(tree: AgentTree): TreeRow[] {
|
||||
const rows: TreeRow[] = [];
|
||||
const root = tree.inspect(tree.rootId);
|
||||
const walk = (record: AgentRecord, ancestors: boolean[], last: boolean, depth: number): void => {
|
||||
const indent = ancestors.map((isLast) => (isLast ? " " : "│ ")).join("");
|
||||
const connector = ancestors.length === 0 ? "" : last ? "└─ " : "├─ ";
|
||||
rows.push({ record, prefix: indent + connector, depth });
|
||||
record.children.forEach((childId, index) => walk(tree.inspect(childId), [...ancestors, last], index === record.children.length - 1, depth + 1));
|
||||
};
|
||||
walk(root, [], false, 0);
|
||||
return rows;
|
||||
}
|
||||
|
||||
const STATUS_COLOR: Record<AgentRecord["status"], (theme: Theme, text: string) => string> = {
|
||||
running: (t, s) => t.fg("accent", s),
|
||||
waiting: (t, s) => t.fg("accent", s),
|
||||
completed: (t, s) => t.fg("success", s),
|
||||
failed: (t, s) => t.fg("error", s),
|
||||
cancelled: (t, s) => t.fg("dim", s),
|
||||
interrupted: (t, s) => t.fg("warning", s),
|
||||
created: (t, s) => t.fg("text", s),
|
||||
archived: (t, s) => t.fg("dim", s),
|
||||
};
|
||||
const STATUS_ICON: Record<AgentRecord["status"], string> = {
|
||||
running: "▸", waiting: "⏸", completed: "✓", failed: "✖", cancelled: "·", interrupted: "⚠", created: "·", archived: "·",
|
||||
};
|
||||
|
||||
function shortName(id: string): string { return id.replace(/-[a-f0-9]{8}$/, ""); }
|
||||
|
||||
function elapsed(record: AgentRecord, now: number): string {
|
||||
if (!record.startedAt) return "";
|
||||
const start = Date.parse(record.startedAt);
|
||||
const end = record.finishedAt ? Date.parse(record.finishedAt) : (record.status === "running" || record.status === "waiting") ? now : undefined;
|
||||
if (!end) return "";
|
||||
const span = Math.max(0, Math.round((end - start) / 1000));
|
||||
return `${String(Math.floor(span / 60)).padStart(2, "0")}:${String(span % 60).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
/** Interactive overlay showing the full agent tree with live statuses, keyboard navigation, and a detail pane. */
|
||||
export function createAgentTreeOverlay(
|
||||
tree: AgentTree,
|
||||
tui: { requestRender(): void },
|
||||
theme: Theme,
|
||||
done: () => void,
|
||||
): { render(width: number): string[]; invalidate(): void; handleInput(data: string): void; dispose(): void } {
|
||||
let selected = 0;
|
||||
let detail = false;
|
||||
const timer = setInterval(() => tui.requestRender(), 1500);
|
||||
return {
|
||||
render(width) {
|
||||
const rows = buildRows(tree);
|
||||
if (selected >= rows.length) selected = Math.max(0, rows.length - 1);
|
||||
const now = Date.now();
|
||||
const lines: string[] = [theme.fg("accent", `◆ Agent tree · ${tree.runId} · ${rows.length - 1} agents`), ""];
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const { record, prefix } = rows[i]!;
|
||||
const marker = i === selected ? theme.fg("accent", ">") : " ";
|
||||
const icon = STATUS_ICON[record.status];
|
||||
const status = STATUS_COLOR[record.status](theme, record.status);
|
||||
const time = elapsed(record, now);
|
||||
lines.push(`${marker} ${prefix}${theme.fg("text", icon)} ${theme.fg("text", shortName(record.id))} ${status}${time ? ` ${theme.fg("dim", time)}` : ""}`);
|
||||
}
|
||||
if (detail && rows[selected]) {
|
||||
const { record } = rows[selected]!;
|
||||
lines.push("", theme.fg("dim", "task:"), ...wrapTextWithAnsi(record.task, width - 2).slice(0, 4).map((line) => theme.fg("text", line)));
|
||||
if (record.result?.summary) lines.push(theme.fg("dim", "result:"), ...wrapTextWithAnsi(record.result.summary, width - 2).slice(0, 5).map((line) => theme.fg("muted", line)));
|
||||
if (record.error) lines.push(theme.fg("error", `error: ${truncateToWidth(record.error, width - 2)}`));
|
||||
}
|
||||
lines.push("", theme.fg("dim", "↑↓ navigate · enter toggle details · esc close"));
|
||||
return lines.map((line) => truncateToWidth(line, width));
|
||||
},
|
||||
invalidate() { /* rebuilt fresh on every render */ },
|
||||
handleInput(data) {
|
||||
const rows = buildRows(tree);
|
||||
if (matchesKey(data, Key.up)) { if (selected > 0) { selected--; tui.requestRender(); } }
|
||||
else if (matchesKey(data, Key.down)) { if (selected < rows.length - 1) { selected++; tui.requestRender(); } }
|
||||
else if (matchesKey(data, Key.enter)) { detail = !detail; tui.requestRender(); }
|
||||
else if (matchesKey(data, Key.escape)) { done(); }
|
||||
},
|
||||
dispose() { clearInterval(timer); },
|
||||
};
|
||||
}
|
||||
@@ -11,7 +11,6 @@ export default function hypothesisMachine(pi: ExtensionAPI): void {
|
||||
pi.on("session_shutdown", async () => { await supervisor.shutdown(); });
|
||||
|
||||
pi.registerCommand("team", { description: "Show the recursive research team", handler: async (_args, ctx) => { ctx.ui.notify(supervisor.team(), "info"); } });
|
||||
pi.registerCommand("agents", { description: "Show the interactive agent tree overlay", handler: async (_args, ctx) => { await supervisor.showAgentTree(ctx); } });
|
||||
pi.registerCommand("research", { description: "Start a bounded research run", handler: async (args, ctx) => { const goal = args.trim(); if (!goal) { ctx.ui.notify("Usage: /research <goal>", "warning"); return; } const state = supervisor.loop; if (!state || !supervisor.tree) throw new Error("Not initialized"); supervisor.tree.setGoal(goal); state.setGoal(goal); state.start(); const prompt = `Research goal: ${goal}\nUse research_control and the recursive agent tools. Create specialized children only when useful. Record each iteration and stop on the coded conditions. Report important progress without flooding the chat.`; if (ctx.isIdle()) pi.sendUserMessage(prompt); else pi.sendUserMessage(prompt, { deliverAs: "followUp" }); } });
|
||||
pi.registerCommand("research-status", { description: "Show research loop state", handler: async (_args, ctx) => { ctx.ui.notify(JSON.stringify(supervisor.loop?.snapshot() ?? {}, null, 2), "info"); } });
|
||||
pi.registerCommand("research-pause", { description: "Pause spawning and the loop", handler: async (_args, ctx) => { if (supervisor.loop?.snapshot().status !== "running") { ctx.ui.notify("Cannot pause a non-running loop", "warning"); return; } supervisor.loop?.pause(); supervisor.tree?.pause(); ctx.ui.notify("Research paused", "info"); } });
|
||||
|
||||
+1
-11
@@ -1,7 +1,6 @@
|
||||
import { resolve } from "node:path";
|
||||
import { defineTool, ModelRuntime, type ExtensionAPI, type ExtensionContext, type ModelRegistry, type Theme, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
||||
import { Text, truncateToWidth } from "@earendil-works/pi-tui";
|
||||
import { createAgentTreeOverlay } from "./agent-tree-ui.js";
|
||||
import { Type } from "typebox";
|
||||
import { StringEnum } from "@earendil-works/pi-ai";
|
||||
import { AgentTree } from "./agent-tree.js";
|
||||
@@ -64,15 +63,6 @@ export class SupervisorIntegration {
|
||||
team(): string { return this.required().tree.render(); }
|
||||
findings(kind?: string): unknown { return this.required().memory.list(kind); }
|
||||
|
||||
/** Open the interactive agent-tree overlay. */
|
||||
async showAgentTree(ctx: ExtensionContext): Promise<void> {
|
||||
const tree = this.required().tree;
|
||||
await ctx.ui.custom<undefined>((tui, theme, _kb, done) => createAgentTreeOverlay(tree, tui, theme, () => done(undefined)), {
|
||||
overlay: true,
|
||||
overlayOptions: { width: "55%", minWidth: 60, maxHeight: "80%", anchor: "top-right", margin: 1, visible: (termWidth) => termWidth >= 60 },
|
||||
});
|
||||
}
|
||||
|
||||
/** Live subagent dashboard widget above the editor, refreshed on a light timer. */
|
||||
private installAgentWidget(ctx: ExtensionContext): void {
|
||||
ctx.ui.setWidget("hm-agents", (tui, theme) => {
|
||||
@@ -93,7 +83,7 @@ export class SupervisorIntegration {
|
||||
const active = agents.filter((agent) => agent.status === "running" || agent.status === "waiting");
|
||||
if (active.length === 0 && state.status !== "running") return [];
|
||||
const now = Date.now();
|
||||
const lines: string[] = [theme.fg("accent", `◆ ${state.runId} · ${state.status} · iter ${state.iteration} · ${active.length} active`) + theme.fg("dim", " /agents")];
|
||||
const lines: string[] = [theme.fg("accent", `◆ ${state.runId} · ${state.status} · iter ${state.iteration} · ${active.length} active`)];
|
||||
for (const agent of active) {
|
||||
const elapsed = agent.startedAt ? Math.max(0, Math.round((now - Date.parse(agent.startedAt)) / 1000)) : 0;
|
||||
const stamp = `${String(Math.floor(elapsed / 60)).padStart(2, "0")}:${String(elapsed % 60).padStart(2, "0")}`;
|
||||
|
||||
Reference in New Issue
Block a user