FN-8221: clear inactive planner overseer state

Clear retained planner overseer state when effective oversight is disabled.

- Remove monitor, recovery, advisor, and dedup runtime for oversight-off tasks.
- Suppress stale oversight-off Eye badges in task cards.
- Cover cleanup and badge behavior with regression tests.
- Document the runtime snapshot invariant and add a patch changeset.

Files changed:
 .changeset/fn-8221-overseer-badge-oversight-off.md |   7 ++
 docs/architecture.md                               |   4 +
 packages/dashboard/app/components/TaskCard.tsx     |   9 +-
 .../app/components/__tests__/TaskCard.test.tsx     |  20 ++++
 .../__tests__/planner-overseer-off-cleanup.test.ts | 119 +++++++++++++++++++++
 packages/engine/src/project-engine.ts              |  12 +++
 6 files changed, 170 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-8221

Fusion-Task-Lineage: 01c9d838-fbe4-4d34-8eb5-d735cf35e581

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-17 11:27:44 -07:00
parent b686fbd61d
commit 5d2c3be6a0
6 changed files with 170 additions and 1 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: The overseer eye badge no longer appears on in-progress/in-review tasks when oversight is off.
category: fix
dev: pollPlannerOverseer now clears retained PlannerOverseerMonitor observations (plus recovery/advisor runtime) when a task's effective plannerOversightLevel resolves to "off", so getPlannerOverseerRuntimeSnapshot returns null and TaskCard omits the Eye badge; TaskCard also guards on oversightLevel !== "off".

View File

@@ -1724,6 +1724,10 @@ block right beside it: any engine error is swallowed and the un-enriched list is
no active observation omit the field entirely (byte-identical payload). `Task.plannerOverseerState?` is a
transient field — engine-populated at serialization time, never persisted to the store or task.json.
When an in-flight task's effective planner oversight resolves to `"off"`,
`ProjectEngine.pollPlannerOverseer` clears retained monitor observations and associated recovery/advisor
runtime in that same poll. The snapshot therefore returns `null` rather than retaining a stale active state.
FN-7516's `TaskCard` renders the badge/affordance; this task only provides the field, the engine
accessor, and (since FN-7516 had not yet landed consumption) a minimal guarded read plus a
memo-comparator entry so the card repaints on state change.

View File

@@ -3135,8 +3135,15 @@ function TaskCardComponent({
`data-planner-overseer-state` attribute in TaskCard.css — do not fork the label
logic here; `plannerOverseerStateLabel`/`plannerOverseerBadgeTooltip` remain the
single source of truth.
FNXC:PlannerOversight 2026-07-17-00:00:
FN-8221 defensively hides a stale non-idle snapshot when its oversight level is off.
The engine clears this runtime at the source, but a client payload must never leak
the Eye badge for an oversight-off in-progress or in-review task.
*/}
{task.plannerOverseerState && task.plannerOverseerState.state !== "idle" && (
{task.plannerOverseerState
&& task.plannerOverseerState.state !== "idle"
&& task.plannerOverseerState.oversightLevel !== "off" && (
<span
className="card-status-badge card-planner-overseer-state"
title={plannerOverseerBadgeTooltip(task.plannerOverseerState, t)}

View File

@@ -320,6 +320,26 @@ describe("TaskCard", () => {
expect(screen.getByTestId("planner-overseer-state-badge")).toBeInTheDocument();
});
it("does not render an overseer badge for a stale non-idle oversight-off snapshot", () => {
const staleOffTask = makeTask({
column: "in-review",
plannerOverseerState: {
state: "watching",
oversightLevel: "off",
watchedStage: "reviewer",
signal: "progressing",
attemptCount: 0,
attemptLimit: 3,
pendingConfirmation: false,
observedAt: 1700000000000,
},
});
render(<TaskCard task={staleOffTask} onOpenDetail={noop} addToast={noop} />);
expect(screen.queryByTestId("planner-overseer-state-badge")).not.toBeInTheDocument();
});
// FN-7563: the badge used to print the raw kebab-case state (e.g.
// "awaiting-confirmation") with a bare "Planner overseer: awaiting-confirmation"
// tooltip. This reproduces the reported in-review symptom and asserts the badge

View File

@@ -0,0 +1,119 @@
/*
* FNXC:PlannerOversight 2026-07-17-00:00:
* FN-8221 exercises ProjectEngine's real poll seam with a narrow in-memory
* workflow-settings store. The retained monitor observation must be cleared
* whenever effective oversight changes to off, regardless of active column or
* whether the value came from the task override or workflow setting.
*/
import { describe, expect, it } from "vitest";
import type { Task } from "@fusion/core";
import { ProjectEngine } from "../project-engine.js";
import { PlannerOverseerMonitor } from "../planner-overseer.js";
type PollStore = {
tasks: Task[];
workflowValues: Record<string, unknown>;
listTasks(input: { column: string }): Promise<Task[]>;
getSettings(): Promise<Record<string, unknown>>;
getTaskWorkflowSelection(taskId: string): { workflowId: string; stepIds: string[] } | undefined;
getWorkflowDefinition(id: string): Promise<undefined>;
getWorkflowSettingsProjectId(): string;
getWorkflowSettingValues(workflowId: string, projectId: string): Record<string, unknown>;
};
function makeStore(tasks: Task[]): PollStore {
return {
tasks,
workflowValues: {},
async listTasks({ column }) {
return this.tasks.filter((task) => task.column === column);
},
async getSettings() {
return {};
},
getTaskWorkflowSelection() {
return { workflowId: "builtin:coding", stepIds: [] };
},
async getWorkflowDefinition() {
return undefined;
},
getWorkflowSettingsProjectId() {
return "fn-8221";
},
getWorkflowSettingValues() {
return this.workflowValues;
},
};
}
function makeTask(id: string, column: "in-progress" | "in-review"): Task {
return {
id,
title: id,
description: "Planner overseer cleanup fixture",
column,
status: column,
priority: "normal",
createdAt: "2026-07-17T00:00:00.000Z",
updatedAt: "2026-07-17T00:00:00.000Z",
} as Task;
}
type PollEngine = {
pollPlannerOverseer(store: PollStore): Promise<void>;
getPlannerOverseerRuntimeSnapshot(taskId: string): ReturnType<ProjectEngine["getPlannerOverseerRuntimeSnapshot"]>;
};
function makeEngine(): PollEngine {
const engine = Object.create(ProjectEngine.prototype) as PollEngine;
(engine as any).plannerOverseer = new PlannerOverseerMonitor();
(engine as any).shuttingDown = false;
(engine as any).plannerRecoveryController = undefined;
(engine as any).sessionAdvisor = undefined;
(engine as any).sessionAdvisorLogCursor = new Map();
(engine as any).plannerObservationEmitDedup = new Map();
(engine as any).plannerEscalationEmitDedup = new Set();
return engine;
}
describe("FN-8221 — oversight-off poll cleanup", () => {
it.each([
{ column: "in-progress" as const, source: "per-task override" as const },
{ column: "in-review" as const, source: "per-task override" as const },
{ column: "in-progress" as const, source: "workflow-effective setting" as const },
{ column: "in-review" as const, source: "workflow-effective setting" as const },
])("clears a retained snapshot when $source resolves to off in $column", async ({ column, source }) => {
const task = makeTask(`${source}-${column}`, column);
const store = makeStore([task]);
const engine = makeEngine();
await engine.pollPlannerOverseer(store);
expect(engine.getPlannerOverseerRuntimeSnapshot(task.id)).toMatchObject({
oversightLevel: "autonomous",
state: "watching",
});
if (source === "per-task override") {
task.plannerOversightLevel = "off";
} else {
store.workflowValues.plannerOversightLevel = "off";
}
await engine.pollPlannerOverseer(store);
expect(engine.getPlannerOverseerRuntimeSnapshot(task.id)).toBeNull();
});
it("retains a non-idle snapshot while oversight remains active", async () => {
const task = makeTask("active-oversight", "in-progress");
const engine = makeEngine();
await engine.pollPlannerOverseer(makeStore([task]));
await engine.pollPlannerOverseer(makeStore([task]));
expect(engine.getPlannerOverseerRuntimeSnapshot(task.id)).toMatchObject({
oversightLevel: "autonomous",
state: "watching",
});
});
});

View File

@@ -2807,6 +2807,18 @@ export class ProjectEngine {
workflowEffective.plannerOversightLevel as string | undefined,
);
if (level === "off") {
/*
* FNXC:PlannerOversight 2026-07-17-00:00:
* FN-8221 requires tasks whose effective oversight resolves to off to discard retained
* observations plus recovery/advisor runtime. This makes getPlannerOverseerRuntimeSnapshot
* return null and prevents the TaskCard eye badge on oversight-off in-progress/in-review tasks.
*/
overseer.clear(task.id);
this.plannerRecoveryController?.clear(task.id);
this.sessionAdvisor?.clear(task.id);
this.sessionAdvisorLogCursor.delete(task.id);
this.plannerObservationEmitDedup.delete(task.id);
this.clearPlannerEscalationDedup(task.id);
continue;
}
// FN-7743: resolve the executor-stall threshold from the task's