From eb67944e682e442d44ecff4588b385578254510c Mon Sep 17 00:00:00 2001 From: alexion Date: Sat, 1 Aug 2026 23:24:16 -0400 Subject: [PATCH] Revert "feat(pi): add read-only subagent attach view" This reverts commit 7bc0d0772c691b8dd43cbdbba0f784221cb9a08c. --- .../agents/pi/extensions/subagents/index.ts | 24 +---- .../pi/extensions/subagents/supervisor.ts | 3 +- .../agents/pi/extensions/subagents/ui.test.ts | 38 +------ modules/agents/pi/extensions/subagents/ui.ts | 98 +------------------ 4 files changed, 4 insertions(+), 159 deletions(-) diff --git a/modules/agents/pi/extensions/subagents/index.ts b/modules/agents/pi/extensions/subagents/index.ts index b7334d6..ea73bca 100644 --- a/modules/agents/pi/extensions/subagents/index.ts +++ b/modules/agents/pi/extensions/subagents/index.ts @@ -6,7 +6,7 @@ import { SubprocessRpcRunner } from "./runner.ts"; import { Supervisor } from "./supervisor.ts"; import { milestoneNotification } from "./status.ts"; import type { SpawnRequest, SubagentStatus } from "./types.ts"; -import { attachedChildView, widget } from "./ui.ts"; +import { widget } from "./ui.ts"; let supervisor: Supervisor | undefined; let lastDiagnostics: Diagnostics = { warnings: [] }; @@ -230,28 +230,6 @@ export default function subagents(pi: ExtensionAPI) { }, }); - pi.registerCommand("subagent-attach", { - description: "Open a read-only attached view for a subagent id", - handler: async (args, ctx) => { - const id = args.trim(); - if (!id) { - ctx.ui.notify("Usage: /subagent-attach ", "warning"); - return; - } - const currentSupervisor = getSupervisor(ctx); - currentSupervisor.status(id); - await ctx.ui.custom((tui, _theme, _keybindings, done) => attachedChildView({ - status: () => currentSupervisor.status(id), - activity: () => currentSupervisor.activity(id), - onDetach: () => done(), - onChange: () => tui.requestRender(), - }), { - overlay: true, - overlayOptions: { width: "90%", maxHeight: "90%", minWidth: 60 }, - }); - }, - }); - pi.registerCommand("subagent-wait", { description: "Wait for subagent ids separated by spaces", handler: async (args, ctx) => { diff --git a/modules/agents/pi/extensions/subagents/supervisor.ts b/modules/agents/pi/extensions/subagents/supervisor.ts index 7073e8c..9bcf5a9 100644 --- a/modules/agents/pi/extensions/subagents/supervisor.ts +++ b/modules/agents/pi/extensions/subagents/supervisor.ts @@ -2,7 +2,6 @@ import type { ChildHandle, ChildRecord, ChildRunner, - SubagentActivityEvent, ContextMode, RunnerActivity, RunnerEvents, @@ -324,7 +323,7 @@ export class Supervisor { return [...this.children.values()].find((child) => child.record === record); } - activity(id: string): SubagentActivityEvent[] { + activity(id: string) { return this.require(id).record.activityEvents.map((event) => ({ ...event })); } diff --git a/modules/agents/pi/extensions/subagents/ui.test.ts b/modules/agents/pi/extensions/subagents/ui.test.ts index 0c7ff57..b659e64 100644 --- a/modules/agents/pi/extensions/subagents/ui.test.ts +++ b/modules/agents/pi/extensions/subagents/ui.test.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; import type { SubagentState, SubagentStatus } from "./types.ts"; -import { attachedChildView, renderAttachedChildView, renderInspector, renderSummary, widget } from "./ui.ts"; +import { renderInspector, renderSummary, widget } from "./ui.ts"; function status(overrides: Partial & { id: string; label: string; state: SubagentState }): SubagentStatus { return { @@ -55,42 +55,6 @@ test("expanded monitor shows concise current activity summaries instead of raw e assert.doesNotMatch(rendered.join("\n"), /message_update|private transcript body/u); }); -test("attached child view is read-only, renders transcript activity, and supports detach plus scrolling", () => { - const child = status({ id: "sg-child", label: "Research worker", state: "running" }); - const activity = Array.from({ length: 24 }, (_, index) => ({ - type: "message_update", - summary: `assistant message ${index + 1}`, - at: `2026-08-01T00:00:${String(index + 1).padStart(2, "0")}.000Z`, - role: "assistant", - text: `captured child message ${index + 1}`, - })); - - const bottom = renderAttachedChildView(child, activity, { width: 100, scrollOffset: 0 }); - assert.match(bottom.join("\n"), /read-only attached view/u); - assert.match(bottom.join("\n"), /Esc\/q detach/u); - assert.match(bottom.join("\n"), /captured child message 24/u); - assert.doesNotMatch(bottom.join("\n"), /> |prompt|send|input channel/ui); - - const scrolled = renderAttachedChildView(child, activity, { width: 100, scrollOffset: 6 }); - assert.match(scrolled.join("\n"), /captured child message 1[0-9]/u); - assert.doesNotMatch(scrolled.join("\n"), /captured child message 24/u); - - let detached = false; - const component = attachedChildView({ - status: () => child, - activity: () => activity, - onDetach: () => { - detached = true; - }, - }); - component.handleInput("\u001b[A"); - assert.doesNotMatch(component.render(100).join("\n"), /captured child message 24/u); - component.handleInput("\u001b[B"); - assert.match(component.render(100).join("\n"), /captured child message 24/u); - component.handleInput("q"); - assert.equal(detached, true); -}); - test("expanded monitor renders one truncated row per child with state, elapsed time, and activity marker", () => { const lines = renderInspector([ status({ diff --git a/modules/agents/pi/extensions/subagents/ui.ts b/modules/agents/pi/extensions/subagents/ui.ts index 2176440..25907c4 100644 --- a/modules/agents/pi/extensions/subagents/ui.ts +++ b/modules/agents/pi/extensions/subagents/ui.ts @@ -1,4 +1,4 @@ -import type { SubagentActivityEvent, SubagentState, SubagentStatus } from "./types.ts"; +import type { SubagentState, SubagentStatus } from "./types.ts"; const COMPACT_GROUPS: Array<{ label: string; states: SubagentState[] }> = [ { label: "queued", states: ["queued"] }, @@ -46,58 +46,6 @@ export function widget(statuses: SubagentStatus[], expanded: boolean) { }); } -export interface AttachedChildViewOptions { - status: () => SubagentStatus; - activity: () => SubagentActivityEvent[]; - onDetach: () => void; - onChange?: () => void; -} - -export function attachedChildView(options: AttachedChildViewOptions) { - let scrollOffset = 0; - return { - invalidate() {}, - render(width: number) { - const status = options.status(); - const activity = options.activity(); - const lines = renderAttachedChildView(status, activity, { width, scrollOffset }); - scrollOffset = clampScrollOffset(scrollOffset, transcriptLines(activity).length, attachedViewportHeight(width)); - return lines; - }, - handleInput(data: string) { - const key = keyName(data); - if (key === "escape" || data === "q") { - options.onDetach(); - return; - } - const viewportHeight = attachedViewportHeight(80); - if (key === "up") scrollOffset += 1; - else if (key === "down") scrollOffset -= 1; - else if (key === "pageup") scrollOffset += viewportHeight; - else if (key === "pagedown") scrollOffset -= viewportHeight; - else return; - scrollOffset = clampScrollOffset(scrollOffset, options.activity().length, viewportHeight); - options.onChange?.(); - }, - }; -} - -export function renderAttachedChildView(status: SubagentStatus, activity: SubagentActivityEvent[], options: { width: number; scrollOffset?: number }): string[] { - const viewportHeight = attachedViewportHeight(options.width); - const body = transcriptLines(activity); - const offset = clampScrollOffset(options.scrollOffset ?? 0, body.length, viewportHeight); - const start = Math.max(0, body.length - viewportHeight - offset); - const visible = body.slice(start, start + viewportHeight); - const scrollHint = body.length > viewportHeight ? ` · ${start + 1}-${start + visible.length}/${body.length}` : ""; - const lines = [ - `subagent ${status.id} · ${status.label} · ${STATE_PRESENTATION[status.state].label}`, - `read-only attached view · ↑/↓ scroll · PgUp/PgDn · Esc/q detach${scrollHint}`, - "", - ...(visible.length > 0 ? visible : ["system no captured child activity yet"]), - ]; - return lines.map((line) => truncateLine(line, options.width)); -} - function renderStatusRow(status: SubagentStatus): string { const presentation = STATE_PRESENTATION[status.state]; const marker = statusMarker(status); @@ -131,47 +79,3 @@ function truncateLine(line: string, width: number): string { if (width === 1) return "…"; return `${line.slice(0, width - 1)}…`; } - -function attachedViewportHeight(width: number): number { - return width < 60 ? 8 : 18; -} - -function transcriptLines(activity: SubagentActivityEvent[]): string[] { - return activity.map((event) => transcriptLine(event)); -} - -function transcriptLine(event: SubagentActivityEvent): string { - if (event.error) return `tool ${event.tool ?? event.type} failed: ${event.error}`; - if (event.tool) return `tool ${event.tool}${event.phase ? ` ${event.phase}` : ""}${valueHint(event.input)}`; - if (event.text) return `${(event.role ?? "assistant").padEnd(8)} ${event.text}`; - if (event.output !== undefined) return `tool ${event.type} output${valueHint(event.output)}`; - return `system ${event.summary}`; -} - -function valueHint(value: unknown): string { - if (value === undefined) return ""; - if (typeof value === "string") return ` ${truncateActivityHint(value)}`; - if (!value || typeof value !== "object" || Array.isArray(value)) return ""; - const path = (value as { path?: unknown }).path; - if (typeof path === "string" && path.trim()) return ` ${path.trim()}`; - const command = (value as { command?: unknown }).command; - if (typeof command === "string" && command.trim()) return ` ${truncateActivityHint(command.trim())}`; - return ""; -} - -function truncateActivityHint(value: string): string { - return value.length <= 80 ? value : `${value.slice(0, 79).trimEnd()}…`; -} - -function clampScrollOffset(offset: number, lineCount: number, viewportHeight: number): number { - return Math.max(0, Math.min(offset, Math.max(0, lineCount - viewportHeight))); -} - -function keyName(data: string): string { - if (data === "\u001b") return "escape"; - if (data === "\u001b[A") return "up"; - if (data === "\u001b[B") return "down"; - if (data === "\u001b[5~") return "pageup"; - if (data === "\u001b[6~") return "pagedown"; - return data; -}