From f22d83cd50f565b002e847ec32e58d1991f63bfa Mon Sep 17 00:00:00 2001 From: wassname <1103714+wassname@users.noreply.github.com> Date: Thu, 10 Sep 2026 18:22:37 +0800 Subject: [PATCH] Keep goal widgets compact by removing subtask lines Preserve tasks in plan files; update README model illustration and prompt link. 176 tests pass, typecheck and lint clean. Co-Authored-By: Pi/OpenAI <288921227+claudypoo@users.noreply.github.com> --- AGENTS.md | 2 +- README.md | 8 +++----- src/prototype.ts | 20 -------------------- test/prototype.test.ts | 14 ++++++++++++-- 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d991f4c..eaf8504 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,7 +70,7 @@ Pi/OpenAI implementation scope: - The supervisor may edit the plan and approve completion after inspecting actual results. It delegates implementation and must not weaken the agreed goal to accept worker output. Keep normal tools; express the division in editable prompts. - State the requested worker model in plan preferences; the supervisor selects it and checks the resolved model. Reuse existing usage displays before adding token-reporting code. - Keep all model-facing prompts in `src/prompts.ts`, in narrative order: planning/interview, Ready, supervision and plan upkeep, check-ins/messages, completion, pause/resume and solo. Make them easy for the user to review and edit. -- Preserve useful features from `main`: plan widgets, progress/subtask visibility, plan-upkeep reminders, high-value planning questions and post-compaction plan context. Check which role needs each feature rather than copying the old supervisor runtime. +- Preserve useful features from `main`: goal widgets, plan-upkeep reminders, high-value planning questions and post-compaction plan context. User update: omit subtasks from widgets; long task text wastes terminal space. Keep tasks in the plan. Check which role needs each feature rather than copying the old supervisor runtime. - Scheduled loops must be visible, editable and removable using the scheduler's own UI. Explain whether each reminder is a scheduled job or an event hook; do not advertise a second timer that does not exist. - Keep an explicit recoverable solo mode: confirm any worker has stopped before allowing the main thread to take over implementation and plan edits. Solo completion is self-verification, not an independent supervisor review. diff --git a/README.md b/README.md index 39b0fd9..ff0141d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Make a short list of goals in one Markdown plan file. The main chat keeps the hi 2026-09-10_15-30-pi-goals -Abridged text from the isolated test, with approval and completion shown together. Paths are shortened; bracketed labels are annotations. Both sessions used DeepSeek in this test. +Abridged text from the isolated test, with approval and completion shown together. Paths are shortened; bracketed labels are annotations. Model names and token counts below illustrate the intended supervisor/worker split, not measurements from this capture. ```text +-----------------------------------------------------------+-----------------------------------------------------------+ @@ -29,12 +29,10 @@ Abridged text from the isolated test, with approval and completion shown togethe | verified-bytes-worker [goals-worker] | | | | | | > | > | -| deepseek-v4-flash-0731 · Fireworks | deepseek-v4-flash-0731 · Fireworks | +| astra · 50k tokens | terra · 200k tokens | +-----------------------------------------------------------+-----------------------------------------------------------+ ``` -[Full captures and verification](slop/reviews/20260910_package-supervision-herdr.md). - The plan file looks like this: ```md @@ -126,7 +124,7 @@ worker later exits. The scheduler deletes disabled jobs on reload. [Test results ## Prompts -Planning, worker and supervisor prompts live in [`src/prompts.ts`](src/prompts.ts), in conversation order. +You can read all the prompts in conversation order in [`src/prompts.ts`](src/prompts.ts). ## Develop diff --git a/src/prototype.ts b/src/prototype.ts index 7b0343e..fe08750 100644 --- a/src/prototype.ts +++ b/src/prototype.ts @@ -53,7 +53,6 @@ interface State { const initial = (): State => ({ mode: "chat", signoffs: {} }); const digest = (text: string) => createHash("sha256").update(text).digest("hex"); const key = (text: string) => text.trim().toLowerCase(); -const SUBTASK_LINE = /^\s+(?:\d+\.|[-*])\s*\[([ xX/-])\]\s*(.*)$/; function goals(text: string) { return foldPlan(text).split("\n").flatMap((line, index) => { const match = GOAL_LINE.exec(line); @@ -62,17 +61,6 @@ function goals(text: string) { return [{ subject: match[2].trim(), status: (box === "x" ? "done" : box === "/" ? "active" : box === "-" ? "cancelled" : "open") as GoalStatus, index }]; }); } -/** Open subtasks under the goal at goalLine, up to the next goal line; the widget shows these. */ -function openSubtasks(plan: string, goalLine: number): string[] { - const lines = foldPlan(plan).split("\n"); - const out: string[] = []; - for (let i = goalLine + 1; i < lines.length; i++) { - if (GOAL_LINE.test(lines[i])) break; - const m = SUBTASK_LINE.exec(lines[i]); - if (m && (m[1] === " " || m[1] === "/")) out.push(m[2].trim()); - } - return out; -} const result = (text: string) => ({ content: [{ type: "text" as const, text }], details: {} }); export default function mainSupervisor(pi: ExtensionAPI) { @@ -126,16 +114,8 @@ export default function mainSupervisor(pi: ExtensionAPI) { } const accepted = items.filter((g) => g.status === "done" && state.signoffs[key(g.subject)]).length; ctx.ui.setStatus("goals", `goals: ${state.child ? "worker" : state.mode} | ${accepted}/${items.length} reviewed`); - // Progress and subtask visibility: the widget is the task list, so the active goal shows its - // next open subtasks without reading archived checkboxes below the fold. - const plan = snapshot.text; - const focus = items.find((g) => g.status === "active") ?? items.find((g) => g.status === "open" && openSubtasks(plan, g.index).length > 0); const mark = (status: GoalStatus, signed: boolean) => status === "done" ? (signed ? "✓" : "?") : status === "active" ? "▸" : status === "cancelled" ? "✗" : "○"; const lines: string[] = items.map((g) => `${mark(g.status, Boolean(state.signoffs[key(g.subject)]))} ${g.subject}`); - if (focus) { - const muted = (s: string) => ctx.ui.theme.fg("muted", ` ◦ ${s}`); - lines.push(...openSubtasks(plan, focus.index).slice(0, 3).map(muted)); - } if (items.some((g) => g.status === "done" && !state.signoffs[key(g.subject)])) lines.push("? = completion claim; parent review still required"); ctx.ui.setWidget("goals", lines); } diff --git a/test/prototype.test.ts b/test/prototype.test.ts index e2b5ad5..dba0e2b 100644 --- a/test/prototype.test.ts +++ b/test/prototype.test.ts @@ -455,7 +455,7 @@ it("cancelled goals do not prevent final cleanup, and solo writes self-verificat expect(text).toContain("Preserved context"); }); -it("lineage-only child explicitly attaches its supplied plan, restores subtasks/context, and cannot complete", async () => { +it("lineage-only child attaches its plan with goal-only widget, retains task context, and cannot complete", async () => { const f = fixture(true); const supplied = join(f.ctx.cwd, "supplied.md"); const text = "- [/] goal: exact file\n - [ ] verify bytes\n## Log\n - [ ] archived task\n"; @@ -466,7 +466,7 @@ it("lineage-only child explicitly attaches its supplied plan, restores subtasks/ await attach.execute("a", { path: "supplied.md" }, undefined, undefined, f.ctx); expect(f.entries.at(-1).data.plan).toBeUndefined(); // no cwd heuristics await attach.execute("a", { path: supplied }, undefined, undefined, f.ctx); - expect(f.ctx.ui.setWidget.mock.lastCall?.[1].join("\n")).toContain("◦ verify bytes"); + expect(f.ctx.ui.setWidget.mock.lastCall?.[1]).toEqual(["▸ exact file"]); expect(f.ctx.ui.setWidget.mock.lastCall?.[1].join("\n")).not.toContain("archived task"); expect(readFileSync(supplied, "utf8")).toBe(text); f.hooks.get("session_start")({}, f.ctx); @@ -475,6 +475,16 @@ it("lineage-only child explicitly attaches its supplied plan, restores subtasks/ expect(completion.content[0].text).toContain("only to the active parent"); }); +it.each(["solo", "supervising"])("%s widget omits long tasks without altering the plan", async mode => { + const f = fixture(); await f.draft(); + const text = "- [/] goal: first output\n - [ ] a long task that should never take widget space\n- [ ] goal: second output\n## Log\n"; + writeFileSync(f.path, text); + if (mode === "solo") { f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped"); await f.command("solo"); } + else await f.command("ready"); + expect(f.ctx.ui.setWidget.mock.lastCall?.[1]).toEqual(["▸ first output", "○ second output"]); + expect(readFileSync(f.path, "utf8")).toBe(text); +}); + it.each(["solo", "supervising"])("%s upkeep is turn-driven, folds Log, resets on working-set edits, and never starts a turn", async mode => { const f = fixture(); await f.draft(); if (mode === "solo") { f.ctx.ui.select.mockResolvedValueOnce("Worker confirmed stopped"); await f.command("solo"); }