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:
gsxdsm
2026-04-22 22:12:19 -07:00
parent 66bb9ea664
commit 21d6703b22
8 changed files with 173 additions and 134 deletions

View File

@@ -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(() => {