fix: --no-auth override, workflow revision in-place fix, state-driven heartbeats
Three orthogonal fixes bundled together so they re-land as a unit after earlier worktree-based reverts kept wiping them individually. 1. `--no-auth` flag now actually disables auth. Previously a stale FUSION_DAEMON_TOKEN in .env silently re-armed bearer-token auth despite the CLI flag. Added a `noAuth` option to ServerOptions; auth-middleware's isDaemonAuthActive/getDaemonToken short-circuit to false/undefined when set; CLI plumbs opts.noAuth through both createServer call sites. 2. Workflow review failures no longer reset every completed step. Previously a single CSS nit from a workflow reviewer could drag 5+ already-approved steps back through plan review, code review, and re-execution because determineRevisionResetStart fuzzy-matched feedback tokens against step names. handleWorkflowRevisionRequest, handleWorkflowStepFailure, and sendTaskBackForFix now call a new reopenLastStepForRevision helper that flips only the last non-pending step back to pending (with currentStep rewind via a newly-accepted updateTask field) — all earlier done steps stay done, and the agent applies the feedback as an in-place patch per the updated PROMPT.md instructions. determineRevisionResetStart stays exported as @deprecated so existing unit tests still link. 3. Heartbeat scheduling is now state-driven. Previously a non-ephemeral agent with a stale runtimeConfig.enabled=false on disk would never tick and the Pause/Resume button couldn't arm the timer without also flipping that hidden flag. HeartbeatTriggerScheduler's watchAgentLifecycle now registers on transitions into active/running and clears on transitions out; the tick and assignment-trigger guards key off state + ephemeral classification. InProcessRuntime's created/updated listeners and startup scan mirror the same semantics. runtimeConfig.enabled is only retained for ephemeral (task-worker) opt-out. Tests updated: agent-heartbeat.test.ts — one test renamed from "skips registration when enabled is false" (obsolete behavior) to "registers regardless of the legacy enabled flag"; 4 assignment-watching tests now pass a realistic `state: "active"` on mock agents. 207 heartbeat tests + 330 executor tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4118,9 +4118,13 @@ describe("HeartbeatTriggerScheduler", () => {
|
||||
expect(scheduler.getRegisteredAgents()).toContain("agent-001");
|
||||
});
|
||||
|
||||
it("skips registration when enabled is false", () => {
|
||||
it("registers regardless of the legacy enabled flag (state is the source of truth)", () => {
|
||||
// runtimeConfig.enabled is no longer honored by the scheduler — pause
|
||||
// and resume happen through agent.state, and the agent:updated listener
|
||||
// drives register/unregister. Callers that still pass `enabled: false`
|
||||
// should not silently lose the timer.
|
||||
scheduler.registerAgent("agent-001", { heartbeatIntervalMs: 10000, enabled: false });
|
||||
expect(scheduler.getRegisteredAgents()).not.toContain("agent-001");
|
||||
expect(scheduler.getRegisteredAgents()).toContain("agent-001");
|
||||
});
|
||||
|
||||
it("applies default 3600-second interval when intervalMs is undefined", async () => {
|
||||
@@ -4486,7 +4490,7 @@ describe("HeartbeatTriggerScheduler", () => {
|
||||
});
|
||||
|
||||
it("triggers callback on agent:assigned event", async () => {
|
||||
const agent = { id: "agent-test", name: "Test", taskId: "FN-001" } as import("@fusion/core").Agent;
|
||||
const agent = { id: "agent-test", name: "Test", state: "active", metadata: {}, taskId: "FN-001" } as import("@fusion/core").Agent;
|
||||
|
||||
eventStore.emit("agent:assigned", agent, "FN-001");
|
||||
|
||||
@@ -4578,7 +4582,7 @@ describe("HeartbeatTriggerScheduler", () => {
|
||||
});
|
||||
(eventStore as any).getBudgetStatus = vi.fn().mockResolvedValue(budgetStatus);
|
||||
|
||||
const agent = { id: "agent-test", name: "Test" } as import("@fusion/core").Agent;
|
||||
const agent = { id: "agent-test", name: "Test", state: "active", metadata: {} } as import("@fusion/core").Agent;
|
||||
eventStore.emit("agent:assigned", agent, "FN-003");
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
@@ -4601,7 +4605,7 @@ describe("HeartbeatTriggerScheduler", () => {
|
||||
});
|
||||
(eventStore as any).getBudgetStatus = vi.fn().mockResolvedValue(budgetStatus);
|
||||
|
||||
const agent = { id: "agent-test", name: "Test" } as import("@fusion/core").Agent;
|
||||
const agent = { id: "agent-test", name: "Test", state: "active", metadata: {} } as import("@fusion/core").Agent;
|
||||
eventStore.emit("agent:assigned", agent, "FN-005");
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
@@ -4636,7 +4640,7 @@ describe("HeartbeatTriggerScheduler", () => {
|
||||
scheduler = new HeartbeatTriggerScheduler(eventStore as unknown as AgentStore, callback, assignmentTaskStore);
|
||||
scheduler.start();
|
||||
|
||||
const agent = { id: "agent-test", name: "Test" } as import("@fusion/core").Agent;
|
||||
const agent = { id: "agent-test", name: "Test", state: "active", metadata: {} } as import("@fusion/core").Agent;
|
||||
eventStore.emit("agent:assigned", agent, "FN-006");
|
||||
|
||||
await vi.waitFor(() => {
|
||||
|
||||
Reference in New Issue
Block a user