From 08e8af604fda7922d02b22d7d32e7f438729fc2c Mon Sep 17 00:00:00 2001 From: wassname2 Date: Tue, 15 Sep 2026 16:25:36 +0800 Subject: [PATCH] Reduce repeated worker notices and shrinking-backlog reminders --- src/index.ts | 13 +++++++------ src/notice-display.ts | 2 +- src/prompts.ts | 6 +++--- test/goals.test.ts | 13 ++++++++++++- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/index.ts b/src/index.ts index b4d9c7d..7a1030c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -357,18 +357,19 @@ export default function mainSupervisor(pi: ExtensionAPI) { } const records = (ctx: ExtensionContext, type: string): T[] => ctx.sessionManager.getBranch().flatMap(entry => entry.type === "custom" && entry.customType === type ? [entry.data as T] : []); const pendingReports = (ctx: ExtensionContext) => records(ctx, REPORT).filter(report => !records(ctx, REVIEW).some(review => review.report === report.id) && !records(ctx, REPORT).some(newer => newer.session === report.session && newer.supersedes === report.id)); - function recordReport(ctx: ExtensionContext, report: Report, wake = true) { + function recordReport(ctx: ExtensionContext, report: Report, wake = true, show = true) { if (records(ctx, REPORT).some(saved => saved.id === report.id)) return; pi.appendEntry(REPORT, report); - send(workerReview(report.plan, report.session, `${report.id}\n${report.text}`), false); + if (show) send(workerReview(report.id, report.text), false); if (wake && ctx.isIdle()) remindReports(ctx); } function remindReports(ctx: ExtensionContext) { if (state.child || state.mode !== "supervising") return; const ids = pendingReports(ctx).map(report => report.id); - const fingerprint = digest(JSON.stringify(ids)); - if (!ids.length || records(ctx, REVIEW_REMINDER).at(-1) === fingerprint) return; - pi.appendEntry(REVIEW_REMINDER, fingerprint); + const branch = ctx.sessionManager.getBranch(); + const sinceReminder = branch.slice(branch.map(entry => entry.type === "custom" ? entry.customType : "").lastIndexOf(REVIEW_REMINDER) + 1); + if (!sinceReminder.some(entry => entry.type === "custom" && entry.customType === REPORT && ids.includes((entry.data as Report).id))) return; + pi.appendEntry(REVIEW_REMINDER); send(pendingReportReviews(ids)); } pi.registerEntryRenderer(REVIEW, entry => new Text((entry.data as ReportReview).content, 0, 0)); @@ -458,7 +459,7 @@ export default function mainSupervisor(pi: ExtensionAPI) { if (retry && records(ctx, REPORT).some(report => report.id === retry && report.text === message.content!.text)) { aliases.set(id, retry); continue; } aliases.set(id, id); const supersedes = message.supersedes ? aliases.get(`${sender}:${message.supersedes}`) || `${sender}:${message.supersedes}` : undefined; - recordReport(ctx, { id, session: sender, sessionFile: owner.worker.sessionFile, plan: owner.plan, requestId: owner.worker.requestId, text: message.content.text, supersedes }, false); + recordReport(ctx, { id, session: sender, sessionFile: owner.worker.sessionFile, plan: owner.plan, requestId: owner.worker.requestId, text: message.content.text, supersedes }, false, false); // Intercom already displays and saves this report. } if (ctx.isIdle()) remindReports(ctx); } diff --git a/src/notice-display.ts b/src/notice-display.ts index 408faaa..13397cb 100644 --- a/src/notice-display.ts +++ b/src/notice-display.ts @@ -10,7 +10,7 @@ export function noticeDisplay(pi: ExtensionAPI) { context.messageType === "user" && mirrored.has(markdown) ? "" : markdown); pi.registerEntryRenderer(NOTICE, (entry, { expanded }, theme) => { const { content } = entry.data as { content: string }; - const label = content.includes("\nPlan changed.") ? "Plan changed · review requested" : "Goal instructions"; + const label = content.includes("\nPlan changed") ? "Plan changed · review requested" : content.includes("\nPending worker reviews:") ? "Worker reviews pending" : "Goal instructions"; if (expanded) return new Markdown(content, 0, 0, getMarkdownTheme()); return { render: (width) => [truncateToWidth(theme.fg("muted", `[pi-goals] ${label} · ${keyHint("app.tools.expand", "expand")}`), width)], diff --git a/src/prompts.ts b/src/prompts.ts index f8fbda0..ca4cf76 100644 --- a/src/prompts.ts +++ b/src/prompts.ts @@ -193,9 +193,9 @@ export function workerAttachment(plan: string, session: string, text: string): s // Pi/OpenAI: supervisor-authored report reviews, separate from goal completion. export const reportReviewDescription = "Review an owned worker report after inspecting its actual artifacts. Quote the assigned goal/task and evidence from files (optional saved-session entryId selects decoded message text). State observations and unmet requirements; use accepted, changes_requested or blocked. Changes requested need a concrete continuation. Text quotes are checked, not their relevance or quality. Non-text evidence needs a nonempty capture and specific observation. Delivery stays pending until the worker saves the visible review. Acceptance never completes a goal or wakes/closes the worker."; export const reportReviewContent = (report: string, sessionFile: string, sources: string[], observation: string, unmet: string, verdict: string, continuation: string) => `Worker review: ${verdict}\nReport: ${report}\nSaved session: ${sessionFile}\n\nAssigned goal/task:\n${sources[0]}\n\nEvidence:\n${sources.slice(1).join("\n\n")}\n\nInspected: ${observation}\nUnmet: ${unmet}\nContinuation: ${continuation || "none"}\nThis is a report review, not CompleteGoal.\n— Pi supervisor`; -export const pendingReportReviews = (reports: string[]) => `Pending worker reviews: ${reports.join(", ")}. Inspect their saved reports and actual artifacts, then use review_subagent. Independent authorized work may continue; receipts and generic replies do not resolve reviews.`; -export function workerReview(plan: string, session: string, text: string): string { - return `Worker event for ${plan}, exact Intercom session ${session}:\n${text}\nThis is a report, not completion approval. Inspect actual artifacts and saved messages; if correction is needed, send it to the same session. Preserve its visible review conversation. Respect pauses; do not reply merely to acknowledge.`; +export const pendingReportReviews = (reports: string[]) => `Pending worker reviews: ${reports.join(", ")}. Inspect reports and artifacts; use review_subagent. Independent authorized work may continue.`; +export function workerReview(report: string, text: string): string { + return `Worker report ${report}:\n${text}`; } export function manualReview(planPath: string, text: string): string { diff --git a/test/goals.test.ts b/test/goals.test.ts index 976bcf5..bfc05fa 100644 --- a/test/goals.test.ts +++ b/test/goals.test.ts @@ -1290,21 +1290,32 @@ it("reviews a saved worker revision through inspection, silent delivery, retry a parent.hooks.get("session_start")({}, parent.ctx); // Reconcile the missed stop from real saved worker history. await parent.command("status"); expect(parent.ctx.ui.notify.mock.lastCall?.[0]).toContain(missed); + await parent.hooks.get("agent_settled")({}, parent.ctx); form.report = missed; await review(); // Old-plan blocked review remains deliverable after retargeting. await parent.command("status"); expect(parent.ctx.ui.notify.mock.lastCall?.[0]).not.toContain(missed); expect(parent.ctx.ui.notify.mock.lastCall?.[0]).toContain(nextReport); + const afterReview = parent.messages.length; + await parent.hooks.get("agent_settled")({}, parent.ctx); + parent.hooks.get("session_start")({}, parent.ctx); + await parent.hooks.get("agent_settled")({}, parent.ctx); + expect(parent.messages).toHaveLength(afterReview); // Shrinking/restoring the same backlog does not wake again. const ordinary = (id: string, text: string, links = {}, sender = workerId) => { const details = { from: { id: sender }, message: { id, timestamp: Date.now(), content: { text }, ...links } }; parent.ctx.sessionManager.getBranch().push({ type: "custom_message", id, customType: "intercom_message", content: text, details }); parent.hooks.get("message_end")({ message: { role: "custom", customType: "intercom_message", content: text, details } }, parent.ctx); }; + const beforeOrdinary = parent.messages.length; ordinary("ordinary-a", "Partial output needs review"); ordinary("ordinary-b", "Corrected output needs review", { supersedes: "ordinary-a" }); ordinary("retry-b", "Corrected output needs review", { retryOf: "ordinary-b" }); ordinary("retry-again", "Corrected output needs review", { retryOf: "retry-b" }); ordinary("ack-only", "OK"); ordinary("foreign", "Unowned report", {}, "foreign-peer"); - parent.hooks.get("session_start")({}, parent.ctx); await parent.command("status"); + expect(parent.messages).toHaveLength(beforeOrdinary); // No second body beside Intercom's saved/displayed original. + parent.hooks.get("session_start")({}, parent.ctx); + await parent.hooks.get("agent_settled")({}, parent.ctx); + expect(parent.messages).toHaveLength(beforeOrdinary + 1); // The new/revised obligation still wakes once. + await parent.command("status"); const pending = parent.ctx.ui.notify.mock.lastCall?.[0]; expect(pending).toContain(`${workerId}:ordinary-b`); for (const excluded of ["ordinary-a", "retry-b", "retry-again", "ack-only", "foreign-peer"]) expect(pending).not.toContain(excluded);