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 3b212b9286
commit 22d31c4cac
8 changed files with 173 additions and 134 deletions

View File

@@ -1,7 +1,7 @@
import type { AddressInfo } from "node:net";
import { randomBytes } from "node:crypto";
import { join } from "node:path";
import { TaskStore, AutomationStore, CentralCore, AgentStore, PluginStore, PluginLoader, getTaskMergeBlocker, getEnabledPiExtensionPaths } from "@fusion/core";
import { TaskStore, AutomationStore, CentralCore, AgentStore, PluginStore, PluginLoader, getTaskMergeBlocker, getEnabledPiExtensionPaths, isEphemeralAgent } from "@fusion/core";
import { createServer, GitHubClient, createSkillsAdapter, getProjectSettingsPath, loadTlsCredentialsFromEnv } from "@fusion/dashboard";
import { aiMergeTask, MissionAutopilot, MissionExecutionLoop, HeartbeatMonitor, HeartbeatTriggerScheduler, type WakeContext, ProjectEngineManager, PeerExchangeService } from "@fusion/engine";
import { AuthStorage, DefaultPackageManager, ModelRegistry, discoverAndLoadExtensions, createExtensionRuntime } from "@mariozechner/pi-coding-agent";
@@ -628,6 +628,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
skillsAdapter,
https: loadTlsCredentialsFromEnv(),
daemon: dashboardAuthToken ? { token: dashboardAuthToken } : undefined,
noAuth: opts.noAuth,
});
const shutdown = async (signal: NodeJS.Signals) => {
@@ -761,14 +762,16 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
const agents = await agentStore.listAgents();
for (const agent of agents) {
// State drives whether a timer is armed. Arm timers only for
// non-ephemeral agents currently in active/running — transitions
// after startup are handled by the scheduler's agent:updated listener.
if (isEphemeralAgent(agent)) continue;
if (agent.state !== "active" && agent.state !== "running") continue;
const rc = agent.runtimeConfig;
if (rc && (rc.heartbeatIntervalMs || rc.enabled !== undefined || rc.maxConcurrentRuns)) {
triggerScheduler.registerAgent(agent.id, {
heartbeatIntervalMs: rc.heartbeatIntervalMs as number | undefined,
enabled: rc.enabled as boolean | undefined,
maxConcurrentRuns: rc.maxConcurrentRuns as number | undefined,
});
}
triggerScheduler.registerAgent(agent.id, {
heartbeatIntervalMs: rc?.heartbeatIntervalMs as number | undefined,
maxConcurrentRuns: rc?.maxConcurrentRuns as number | undefined,
});
}
if (agents.length > 0) {
console.log(`[engine] Registered ${triggerScheduler.getRegisteredAgents().length} agents for heartbeat triggers`);
@@ -810,6 +813,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
skillsAdapter,
https: loadTlsCredentialsFromEnv(),
daemon: dashboardAuthToken ? { token: dashboardAuthToken } : undefined,
noAuth: opts.noAuth,
});
}