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>
This commit is contained in:
wassname
2026-09-10 18:22:37 +08:00
co-authored by Pi/OpenAI
parent c175096ddb
commit f22d83cd50
4 changed files with 16 additions and 28 deletions
+1 -1
View File
@@ -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.
+3 -5
View File
@@ -4,7 +4,7 @@ Make a short list of goals in one Markdown plan file. The main chat keeps the hi
<img width="2513" height="1259" alt="2026-09-10_15-30-pi-goals" src="https://github.com/user-attachments/assets/35feaa15-f022-4491-bcc2-fc31cb878a9f" />
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
-20
View File
@@ -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);
}
+12 -2
View File
@@ -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"); }