feat(FN-2956): merge fusion/fn-2956
- **FN-2956: Node routing policy guard** — `scheduler.ts` and `agent-heartbeat.ts` now enforce unavailable-node dispatch prevention; adds policy-matrix unit tests (`node-routing-policy.test.ts`) and integration tests (`scheduler-node-routing.test.ts`) - **Agent delegation tools** — `agent-tools.ts` gains delegation functionality; `agent-heartbeat.ts` updated with monitoring hooks; 37 new lines of test coverage in `agent-tools.test.ts` - **Dashboard route registration** — New routes added for git/github registration (`register-git-github.ts`), planning subtasks, and task workflows; test coverage in `routes.test.ts` - **CLI task planning** — `task.ts` and `extension.ts` updated to support task planning flow; task-plan test extended - **Engine integrations** — `cron-runner.ts`, `pr-comment-handler.ts`, `triage.ts`, `mission-execution-loop.ts`, `project-engine.ts`, and `routine-runner.ts` all updated with expanded tests and feature hooks - **Dashboard UI/hooks** — `useTaskHandlers.ts` and related tests updated; `ModelOnboardingModal.tsx` and `TodoView.tsx` refined; CSS adjustments in `AgentsView.css` - Includes merged work from `fusion/fn-2923` and `fusion/fn-2945` Commits merged: - test(FN-2956): complete Step 4 — align scheduler routing policy integration tests - test(FN-2956): complete Step 3 — expand policy matrix unit coverage - feat(FN-2956): complete Step 2 — enforce unavailable-node policy dispatch guard - feat(FN-2923): merge fusion/fn-2923 - feat(FN-2945): merge fusion/fn-2945 Files changed: packages/cli/src/__tests__/task-plan.test.ts | 1 + packages/cli/src/commands/__tests__/task.test.ts | 15 +++++++-- packages/cli/src/commands/task.ts | 15 ++++++++- packages/cli/src/extension.ts | 9 ++++++ .../app/__tests__/agent-css-classes.test.ts | 3 +- packages/dashboard/app/__tests__/api.test.ts | 13 ++++++++ packages/dashboard/app/api/legacy.ts | 2 ++ packages/dashboard/app/components/AgentsView.css | 22 ++++++------- .../app/components/ModelOnboardingModal.tsx | 5 ++- packages/dashboard/app/components/TodoView.tsx | 2 ++ .../__tests__/ModelOnboardingModal.test.tsx | 4 +-- .../app/components/__tests__/QuickChatFAB.test.tsx | 14 ++------ .../app/components/__tests__/TodoView.test.tsx | 4 +-- .../__tests__/agents-view-mobile.test.tsx | 5 ++- .../components/__tests__/onboarding-flow.test.tsx | 2 +- .../app/hooks/__tests__/useTaskHandlers.test.ts | 4 +-- packages/dashboard/app/hooks/useTaskHandlers.ts | 4 +-- packages/dashboard/src/__tests__/routes.test.ts | 14 ++++++++ packages/dashboard/src/routes.ts | 4 +++ .../dashboard/src/routes/register-git-github.ts | 12 +++++++ .../src/routes/register-planning-subtask-routes.ts | 3 ++ .../src/routes/register-task-workflow-routes.ts | 7 ++++ .../engine/src/__tests__/agent-heartbeat.test.ts | 10 ++++++ .../src/__tests__/agent-tools-delegation.test.ts | 2 ++ packages/engine/src/__tests__/agent-tools.test.ts | 37 ++++++++++++++++++++++ packages/engine/src/__tests__/cron-runner.test.ts | 4 +++ .../src/__tests__/node-routing-policy.test.ts | 25 +++++++++++++-- .../src/__tests__/pr-comment-handler.test.ts | 8 +++++ .../src/__tests__/scheduler-node-routing.test.ts | 18 +++++------ packages/engine/src/__tests__/triage.test.ts | 2 ++ packages/engine/src/agent-heartbeat.ts | 13 ++++++-- packages/engine/src/agent-tools.ts | 13 ++++++-- packages/engine/src/cron-runner.ts | 7 +++- packages/engine/src/executor.ts | 2 +- packages/engine/src/mission-execution-loop.ts | 8 +++++ packages/engine/src/pr-comment-handler.ts | 5 +++ packages/engine/src/project-engine.ts | 8 +++++ packages/engine/src/routine-runner.ts | 4 +++ packages/engine/src/scheduler.ts | 8 +++-- packages/engine/src/triage.ts | 4 +++ 40 files changed, 280 insertions(+), 62 deletions(-) Fusion-Task-Id: FN-2956
This commit is contained in:
@@ -61,9 +61,9 @@ function createMockStore(task: Task, settings: Record<string, unknown> = {}): Ta
|
||||
} as unknown as TaskStore;
|
||||
}
|
||||
|
||||
function createMockNodeHealthMonitor(healthMap: Record<string, NodeStatus | undefined>) {
|
||||
function createMockHealthMonitor(statusMap: Record<string, NodeStatus | undefined>) {
|
||||
return {
|
||||
getNodeHealth: vi.fn((id: string) => healthMap[id]),
|
||||
getNodeHealth: vi.fn((id: string) => statusMap[id]),
|
||||
} as unknown as import("../node-health-monitor.js").NodeHealthMonitor;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ describe("Scheduler node routing", () => {
|
||||
it("blocks dispatch when node is unhealthy and policy is block", async () => {
|
||||
const task = createMockTask({ id: "FN-104", nodeId: "node-offline" });
|
||||
const store = createMockStore(task, { maxConcurrent: 1, maxWorktrees: 1, unavailableNodePolicy: "block" });
|
||||
const healthMonitor = createMockNodeHealthMonitor({ "node-offline": "offline" });
|
||||
const healthMonitor = createMockHealthMonitor({ "node-offline": "offline" });
|
||||
const scheduler = new Scheduler(store, { nodeHealthMonitor: healthMonitor });
|
||||
(scheduler as unknown as { running: boolean }).running = true;
|
||||
|
||||
@@ -146,13 +146,13 @@ describe("Scheduler node routing", () => {
|
||||
expect(store.updateTask).not.toHaveBeenCalled();
|
||||
expect(store.moveTask).not.toHaveBeenCalled();
|
||||
expect(store.logEntry).toHaveBeenCalledWith(task.id, "Node node-offline is offline; policy is block");
|
||||
expect(schedulerLog.warn).toHaveBeenCalledWith("Task FN-104 blocked: Node node-offline is offline; policy is block");
|
||||
expect(schedulerLog.log).toHaveBeenCalledWith("Task FN-104 dispatch blocked — Node node-offline is offline; policy is block");
|
||||
});
|
||||
|
||||
it("deduplicates blocked log entries across polling cycles", async () => {
|
||||
const task = createMockTask({ id: "FN-105", nodeId: "node-offline" });
|
||||
const store = createMockStore(task, { maxConcurrent: 1, maxWorktrees: 1, unavailableNodePolicy: "block" });
|
||||
const healthMonitor = createMockNodeHealthMonitor({ "node-offline": "offline" });
|
||||
const healthMonitor = createMockHealthMonitor({ "node-offline": "offline" });
|
||||
const scheduler = new Scheduler(store, { nodeHealthMonitor: healthMonitor });
|
||||
(scheduler as unknown as { running: boolean }).running = true;
|
||||
|
||||
@@ -168,7 +168,7 @@ describe("Scheduler node routing", () => {
|
||||
it("falls back to local dispatch when node is unhealthy and policy is fallback-local", async () => {
|
||||
const task = createMockTask({ id: "FN-106", nodeId: "node-error" });
|
||||
const store = createMockStore(task, { maxConcurrent: 1, maxWorktrees: 1, unavailableNodePolicy: "fallback-local" });
|
||||
const healthMonitor = createMockNodeHealthMonitor({ "node-error": "error" });
|
||||
const healthMonitor = createMockHealthMonitor({ "node-error": "error" });
|
||||
const scheduler = new Scheduler(store, { nodeHealthMonitor: healthMonitor });
|
||||
(scheduler as unknown as { running: boolean }).running = true;
|
||||
|
||||
@@ -184,7 +184,7 @@ describe("Scheduler node routing", () => {
|
||||
it("dispatches normally when node is online with block policy", async () => {
|
||||
const task = createMockTask({ id: "FN-107", nodeId: "node-online" });
|
||||
const store = createMockStore(task, { maxConcurrent: 1, maxWorktrees: 1, unavailableNodePolicy: "block" });
|
||||
const healthMonitor = createMockNodeHealthMonitor({ "node-online": "online" });
|
||||
const healthMonitor = createMockHealthMonitor({ "node-online": "online" });
|
||||
const scheduler = new Scheduler(store, { nodeHealthMonitor: healthMonitor });
|
||||
(scheduler as unknown as { running: boolean }).running = true;
|
||||
|
||||
@@ -199,7 +199,7 @@ describe("Scheduler node routing", () => {
|
||||
it("dispatches normally when node health is unknown", async () => {
|
||||
const task = createMockTask({ id: "FN-108", nodeId: "node-unknown" });
|
||||
const store = createMockStore(task, { maxConcurrent: 1, maxWorktrees: 1, unavailableNodePolicy: "block" });
|
||||
const healthMonitor = createMockNodeHealthMonitor({ "node-unknown": undefined });
|
||||
const healthMonitor = createMockHealthMonitor({ "node-unknown": undefined });
|
||||
const scheduler = new Scheduler(store, { nodeHealthMonitor: healthMonitor });
|
||||
(scheduler as unknown as { running: boolean }).running = true;
|
||||
|
||||
@@ -251,7 +251,7 @@ describe("Scheduler node routing", () => {
|
||||
it("never queries health for local tasks", async () => {
|
||||
const task = createMockTask({ id: "FN-111", nodeId: undefined });
|
||||
const store = createMockStore(task, { maxConcurrent: 1, maxWorktrees: 1 });
|
||||
const healthMonitor = createMockNodeHealthMonitor({});
|
||||
const healthMonitor = createMockHealthMonitor({});
|
||||
const scheduler = new Scheduler(store, { nodeHealthMonitor: healthMonitor });
|
||||
(scheduler as unknown as { running: boolean }).running = true;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user