Send incremental worker views with direction and tracked job state

Borrow the provider tracker queries from cecb1e9. Keep unavailable state unknown, bind incremental views to acknowledged source entries, reset after compaction, bound serialized payloads, and recheck tracked work at sign-off.
This commit is contained in:
wassname
2026-09-08 16:41:47 +08:00
parent 489298d58b
commit 47cc054582
13 changed files with 305 additions and 31 deletions
+31
View File
@@ -0,0 +1,31 @@
import { EventEmitter } from "node:events";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { expect, it, vi } from "vitest";
import { backgroundState } from "../src/background.js";
function api(tools: string[], processes?: unknown, subagents?: number) {
const bus = new EventEmitter();
if (processes !== undefined) bus.on("processes:request:list", request => request.reply(processes));
if (subagents !== undefined) bus.on("subagents:rpc:v1:request", request => bus.emit(`subagents:rpc:v1:reply:${request.requestId}`, { requestId: request.requestId, success: true, data: { fleet: { version: 1, totalActive: subagents } } }));
return { getAllTools: () => tools.map(name => ({ name })), events: { emit: (name: string, value: unknown) => bus.emit(name, value), on: (name: string, fn: (...args: any[]) => void) => { bus.on(name, fn); return () => { bus.off(name, fn); }; } } } as unknown as ExtensionAPI;
}
it("reports tracked running work, rather than equating idle agent with finished jobs", async () => {
const active = await backgroundState(api(["process", "subagent"], [{ name: "generation", status: "running" }], 1));
expect(active.quiet).toBe(false);
expect(active.description).toContain("processes: 1 (generation)");
expect(active.description).toContain("subagents: 1");
const finished = await backgroundState(api(["process", "subagent"], [{ status: "exited" }], 0));
expect(finished.quiet).toBe(true);
});
it("distinguishes missing providers from an unavailable installed tracker", async () => {
expect((await backgroundState(api([]))).quiet).toBe(true);
expect(await backgroundState(api(["process"]))).toMatchObject({ quiet: false, description: expect.stringContaining("processes: unknown") });
vi.useFakeTimers();
try {
const unavailable = backgroundState(api(["subagent"]));
await vi.advanceTimersByTimeAsync(2000);
expect(await unavailable).toMatchObject({ quiet: false, description: expect.stringContaining("subagents: unknown") });
} finally { vi.useRealTimers(); }
});
+11
View File
@@ -52,6 +52,17 @@ describe("pi-intercom transport", () => {
expect(resumed.fixture.sent.filter(message => message.kind === "steer")).toHaveLength(1);
});
it("advances the incremental overview only after acknowledgment", async () => {
const runtime = setup("worker");
await runtime.link.waitReady();
const view = runtime.link.view("The worker stopped.", "settled", "entry-1", true);
expect(runtime.link.acknowledgedEntry).toBeUndefined();
runtime.fixture.receive({ binding: "binding", role: "supervisor", kind: "received", id: view.id });
expect(runtime.link.acknowledgedEntry).toBe("entry-1");
const resumed = setup("worker", [...runtime.entries]);
expect(resumed.link.acknowledgedEntry).toBe("entry-1");
});
it("cancels a readiness wait on shutdown", async () => {
const runtime = setup("worker");
await runtime.link.waitReady();
+7 -2
View File
@@ -49,8 +49,8 @@ function setup(cwd: string, planPath: string, tokens: number | null = 10, onComp
activeTools: () => activeTools, branch: (value: any[]) => { branch = value; }, ctx, entries, hooks, transport, messages, tools,
ready: () => transport.sent.some(message => message.kind === "hello" && message.role === "supervisor" && message.ready),
start: async () => { await hooks.get("session_start")({}, ctx); await new Promise(resolve => setImmediate(resolve)); },
view: (id: string, text: string, reason = "settled") => {
transport.receive({ binding: "approval-1", role: "worker", kind: "view", id, text, reason });
view: (id: string, text: string, reason = "settled", backgroundQuiet = true) => {
transport.receive({ binding: "approval-1", role: "worker", kind: "view", id, text, reason, backgroundQuiet });
return { text };
},
};
@@ -216,6 +216,11 @@ describe("visible supervisor session", () => {
const stale = await runtime.tools.get("ApproveGoal").execute("id", { goal: "make the file", verifyOutputPath: "verify.txt" }, undefined, undefined, runtime.ctx);
expect(stale.isError).toBe(true);
expect(stale.content[0].text).toContain("latest worker view");
const unknown = runtime.view("third", "The worker stopped.\ntracked background work: unknown", "settled", false);
runtime.branch([{ type: "message", message: { role: "user", content: [{ type: "text", text: unknown.text }] } }]);
const blocked = await runtime.tools.get("ApproveGoal").execute("id", { goal: "make the file", verifyOutputPath: "verify.txt" }, undefined, undefined, runtime.ctx);
expect(blocked.isError).toBe(true);
expect(blocked.content[0].text).toContain("background work is active or unknown");
} finally { rmSync(cwd, { recursive: true, force: true }); }
});
});
+37
View File
@@ -0,0 +1,37 @@
import { expect, it } from "vitest";
import { workerView } from "../src/worker-view.js";
const context = { sourceSession: "/sessions/worker.jsonl", latestDirection: "Modal uses a remote GPU.", model: "provider/worker", background: "processes: 0; subagents: 0" };
const entry = (id: string, text: string) => ({ id, type: "message", message: { role: "assistant", content: text } });
it("keeps human direction and source location while sending only new messages", () => {
const view = workerView([entry("old", "old detail"), entry("new", "new result")], "interval", true, { ...context, since: "old" });
expect(view).toContain(context.latestDirection);
expect(view).toContain(context.sourceSession);
expect(view).toContain("new result");
expect(view).not.toContain("old detail");
});
it("restarts after compaction and does not report historical tool calls as active", () => {
const entries = [
{ id: "old", type: "message", message: { role: "assistant", content: [{ type: "toolCall", id: "call", name: "edit" }] } },
{ id: "compact", type: "compaction", summary: "Saved worker account." },
entry("new", "new result"),
];
const initial = workerView(entries, "interval", true, { ...context, since: "old" });
expect(initial).toContain("Saved worker account.");
expect(initial).toContain("tool calls with no result: none");
const next = workerView(entries, "interval", true, { ...context, since: "new" });
expect(next).not.toContain("Saved worker account.");
expect(next).toContain("No new messages.");
});
it("bounds serialized Unicode and quoted logs while marking omissions", () => {
const view = workerView([
{ id: "compact", type: "compaction", summary: '"\\🧪'.repeat(20_000) },
entry("new", '"\\🧪'.repeat(20_000)),
], "interval", true, { ...context, latestDirection: "Remote only. ".repeat(3000) });
expect(Buffer.byteLength(JSON.stringify({ binding: "binding", role: "worker", kind: "view", id: "id", text: view }))).toBeLessThan(16_000);
expect(view).toContain("[truncated; inspect source session]");
expect(view).toContain(context.sourceSession);
});