feat(FN-3282): add durable review addressing snapshots

This merge adds two substantial features: a rooms and chat sidebar system (FN-3806) with a new `CreateRoomModal` and enriched `ChatView`, wired through plugin routes and a new `listTasksModifiedSince` core API; and a durable review addressing workflow (FN-3282) that persists snapshots, renders addre

Fusion-Task-Id: FN-3282
This commit is contained in:
Fusion
2026-05-09 02:32:38 -07:00
committed by gsxdsm
parent dd295bdcdc
commit f1ad586774
9 changed files with 360 additions and 109 deletions

View File

@@ -151,6 +151,58 @@ describe("buildExecutionPrompt", () => {
});
});
describe("TaskExecutor review addressing transitions", () => {
beforeEach(() => {
resetExecutorMocks();
});
it("moves queued addressing records to in-progress", async () => {
const store = createMockStore();
store.getTask.mockResolvedValue({
id: "FN-001",
column: "in-progress",
status: null,
reviewState: {
source: "pull-request",
items: [],
addressing: [{ itemId: "ri-1", status: "queued", selectedAt: new Date().toISOString() }],
},
});
const executor = new TaskExecutor(store, "/tmp/test");
await (executor as any).transitionReviewAddressing("FN-001", ["queued"], "in-progress");
expect(store.updateTask).toHaveBeenCalledWith("FN-001", {
reviewState: expect.objectContaining({
addressing: [expect.objectContaining({ status: "in-progress", startedAt: expect.any(String) })],
}),
});
});
it("marks in-progress addressing records as failed", async () => {
const store = createMockStore();
store.getTask.mockResolvedValue({
id: "FN-001",
column: "in-review",
status: "failed",
reviewState: {
source: "reviewer-agent",
items: [],
addressing: [{ itemId: "ri-1", status: "in-progress", selectedAt: new Date().toISOString() }],
},
});
const executor = new TaskExecutor(store, "/tmp/test");
await (executor as any).transitionReviewAddressing("FN-001", ["in-progress"], "failed");
expect(store.updateTask).toHaveBeenCalledWith("FN-001", {
reviewState: expect.objectContaining({
addressing: [expect.objectContaining({ status: "failed", completedAt: expect.any(String) })],
}),
});
});
});
describe("TaskExecutor action gate context", () => {
it("pauses task and agent for approval and marks completion", async () => {
const store = createMockStore();

View File

@@ -2266,8 +2266,11 @@ export class TaskExecutor {
// true = requeue to todo, false = budget exhausted (already marked failed).
let stuckRequeue: boolean | null = null;
let taskDone = false;
let reviewAddressingActivated = false;
try {
await this.transitionReviewAddressing(task.id, ["queued"], "in-progress");
reviewAddressingActivated = true;
// Check dependencies
const allTasks = await this.store.listTasks({ slim: true, includeArchived: false });
const unmetDeps = task.dependencies.filter((depId) => {
@@ -3823,6 +3826,15 @@ export class TaskExecutor {
this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
}
} finally {
if (reviewAddressingActivated) {
const latestTask = await this.store.getTask(task.id);
if (taskDone) {
await this.transitionReviewAddressing(task.id, ["in-progress", "queued"], "addressed");
} else if (latestTask.status === "failed") {
await this.transitionReviewAddressing(task.id, ["in-progress", "queued"], "failed");
}
}
this.executing.delete(task.id);
// Clear run context at end of execute() lifecycle
this.currentRunContext = undefined;
@@ -4130,6 +4142,41 @@ export class TaskExecutor {
};
}
private async transitionReviewAddressing(taskId: string, from: Array<"queued" | "in-progress" | "addressed" | "failed">, to: "queued" | "in-progress" | "addressed" | "failed"): Promise<void> {
const task = await this.store.getTask(taskId);
const reviewState = task.reviewState;
if (!reviewState || reviewState.addressing.length === 0) {
return;
}
const now = new Date().toISOString();
let changed = false;
const addressing = reviewState.addressing.map((record) => {
if (!from.includes(record.status)) {
return record;
}
changed = true;
return {
...record,
status: to,
startedAt: to === "in-progress" ? now : record.startedAt,
completedAt: to === "addressed" || to === "failed" ? now : record.completedAt,
error: to === "addressed" ? undefined : record.error,
};
});
if (!changed) {
return;
}
await this.store.updateTask(taskId, {
reviewState: {
...reviewState,
addressing,
},
});
}
private createTaskDoneTool(taskId: string, onDone: () => void): ToolDefinition {
const store = this.store;
return {