feat(FN-4137): add review markdown toggle to document view (FN-4137)

This large merge delivers substantial improvements across the dashboard, engine, and CI infrastructure. Key themes include GitHub tracking feature parity (mobile layout, card icons, header alignment, title derivation, restart persistence, untitled issue handling), chat/room UX enhancements (draft au

Fusion-Task-Id: FN-4137
This commit is contained in:
Fusion
2026-05-12 20:47:07 -07:00
committed by gsxdsm
parent 8220ec66b7
commit ab8f3a5630
12 changed files with 364 additions and 25 deletions

View File

@@ -2111,8 +2111,8 @@ describe("TaskExecutor global pause behavior", () => {
await executor.execute(todoTask as any);
expect(store.updateTask).toHaveBeenCalledWith("FN-001", {
paused: undefined,
pausedByAgentId: undefined,
paused: false,
pausedByAgentId: null,
status: null,
});
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "in-progress");
@@ -2187,8 +2187,8 @@ describe("TaskExecutor global pause behavior", () => {
// FN-4145: explicit agent completion always clears task-level pause state.
expect(store.updateTask).toHaveBeenCalledWith("FN-001", {
paused: undefined,
pausedByAgentId: undefined,
paused: false,
pausedByAgentId: null,
status: null,
});
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "in-progress");
@@ -2257,8 +2257,8 @@ describe("TaskExecutor global pause behavior", () => {
await executor.execute(inProgressTask as any);
expect(store.updateTask).toHaveBeenCalledWith("FN-001", {
paused: undefined,
pausedByAgentId: undefined,
paused: false,
pausedByAgentId: null,
status: null,
});
expect(watchdogSpy).toHaveBeenCalledWith("FN-001", "fn_task_done");
@@ -2279,6 +2279,143 @@ describe("TaskExecutor global pause behavior", () => {
});
});
describe("fn_task_done with paused state (FN-3964 / FN-4167 regression)", () => {
it("advances todo + paused tasks through normal completion handoff", async () => {
const store = createMockStore();
let capturedCustomTools: any[] = [];
const todoTask = {
id: "FN-001",
title: "Paused todo task",
description: "T",
prompt: "# test\n## Steps\n### Step 0: Preflight\n- [ ] check",
column: "todo",
paused: true,
pausedByAgentId: "agent-123",
dependencies: [],
steps: [{ name: "Step 1", status: "pending" }],
currentStep: 0,
log: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
store.getTask.mockResolvedValue(todoTask);
store.getSettings.mockResolvedValue({
maxConcurrent: 2,
maxWorktrees: 4,
pollIntervalMs: 15000,
autoMerge: false,
globalPause: false,
enginePaused: false,
});
store.moveTask.mockImplementation(async (_id: string, to: string) => ({ ...todoTask, column: to, paused: false }));
mockedCreateFnAgent.mockImplementation((async (opts: any) => {
capturedCustomTools = opts.customTools || [];
return {
session: {
prompt: vi.fn().mockImplementation(async () => {
const taskDoneTool = capturedCustomTools.find((tool: any) => tool.name === "fn_task_done");
if (taskDoneTool) {
await taskDoneTool.execute("call-1", { summary: "done" });
}
}),
dispose: vi.fn(),
},
};
}) as any);
const executor = new TaskExecutor(store, "/tmp/test");
const watchdogSpy = vi.spyOn(executor as any, "scheduleCompletedTaskWatchdog");
await executor.execute(todoTask as any);
expect(store.updateTask).toHaveBeenCalledWith("FN-001", {
paused: false,
pausedByAgentId: null,
status: null,
});
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "in-progress");
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "in-review");
expect(watchdogSpy).toHaveBeenCalledWith("FN-001", "fn_task_done");
expect(
store.logEntry.mock.calls.some(
([id, action]: [string, string]) =>
id === "FN-001" && action.includes("Completion handoff deferred — global pause active"),
),
).toBe(false);
// globalPause:true deferred behavior is intentionally covered by the test above.
});
it("completes in-progress + paused tasks after clearing task-level pause state", async () => {
const store = createMockStore();
let capturedCustomTools: any[] = [];
const inProgressTask = {
id: "FN-001",
title: "Paused in-progress task",
description: "T",
prompt: "# test\n## Steps\n### Step 0: Preflight\n- [ ] check",
column: "in-progress",
paused: true,
pausedByAgentId: "agent-123",
dependencies: [],
steps: [{ name: "Step 1", status: "pending" }],
currentStep: 0,
log: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
store.getTask.mockResolvedValue(inProgressTask);
store.getSettings.mockResolvedValue({
maxConcurrent: 2,
maxWorktrees: 4,
pollIntervalMs: 15000,
autoMerge: false,
globalPause: false,
enginePaused: false,
});
mockedCreateFnAgent.mockImplementation((async (opts: any) => {
capturedCustomTools = opts.customTools || [];
return {
session: {
prompt: vi.fn().mockImplementation(async () => {
const taskDoneTool = capturedCustomTools.find((tool: any) => tool.name === "fn_task_done");
if (taskDoneTool) {
await taskDoneTool.execute("call-1", { summary: "done" });
}
}),
dispose: vi.fn(),
},
};
}) as any);
const executor = new TaskExecutor(store, "/tmp/test");
const watchdogSpy = vi.spyOn(executor as any, "scheduleCompletedTaskWatchdog");
await executor.execute(inProgressTask as any);
expect(store.updateTask).toHaveBeenCalledWith("FN-001", {
paused: false,
pausedByAgentId: null,
status: null,
});
expect(watchdogSpy).toHaveBeenCalledWith("FN-001", "fn_task_done");
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "in-review");
expect(store.moveTask).not.toHaveBeenCalledWith("FN-001", "todo");
expect(store.moveTask).not.toHaveBeenCalledWith("FN-001", "in-progress");
expect(
store.logEntry.mock.calls.some(
([id, action]: [string, string]) =>
id === "FN-001" && action.includes("Completion handoff deferred — global pause active"),
),
).toBe(false);
// globalPause:true deferred behavior is intentionally covered by
// "parks todo tasks in in-progress when fn_task_done is called during global pause".
});
});
it("takes no action when globalPause remains false", async () => {
const store = createMockStore();
const disposeFn = vi.fn();

View File

@@ -135,7 +135,7 @@ describe("TaskExecutor enginePaused soft pause (no agent termination)", () => {
expect(watchdogSpy).toHaveBeenCalledWith("FN-001", "fn_task_done");
expect(store.updateTask).toHaveBeenCalledWith(
"FN-001",
expect.objectContaining({ paused: undefined, pausedByAgentId: undefined, status: null }),
expect.objectContaining({ paused: false, pausedByAgentId: null, status: null }),
);
expect(store.moveTask).toHaveBeenCalledWith("FN-001", "in-review");
});

View File

@@ -4599,8 +4599,8 @@ export class TaskExecutor {
// in-flight work. Always clear it on explicit agent completion so the
// board cannot strand a completed task in a paused state.
await store.updateTask(taskId, {
paused: undefined,
pausedByAgentId: undefined,
paused: false,
pausedByAgentId: null,
status: null,
});
await store.logEntry(taskId, "Task marked done by agent");