Each agent now gets its own .fusion/agents/<id>/HEARTBEAT.md procedure
file instead of sharing a single project-wide file. A one-shot
migration in AgentStore.init() re-points existing agents off the legacy
shared path and copies the legacy file's contents into each agent's
new per-agent location so operator edits are preserved.
The HeartbeatTriggerScheduler now phase-aligns the first tick to
lastHeartbeatAt + intervalMs so a process restart resumes each agent's
existing schedule rather than waiting up to a full interval before
firing again. Overdue ticks fire promptly within a small jitter window
to avoid a thundering herd at boot.
Also fixes three pre-existing QuickChatFAB test failures introduced by
ad4db8243: auto-select default model now switches to model mode whether
or not agents are present, the model tag only renders in model mode,
and one test scopes its option lookup to role="option" to disambiguate
the in-header tag from the dropdown entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dashboard API route registrars
packages/dashboard/src/routes.ts remains the single public entrypoint (createApiRoutes(store, options)), but route definitions are registered by domain modules in this directory.
Shared context contract
All registrars receive ApiRoutesContext from ./types.ts, built by createApiRoutesContext() in ./context.ts.
Registrars should be typed as ApiRouteRegistrar so modules share one explicit registration contract.
The context centralizes cross-cutting dependencies so registrars preserve behavior without re-implementing plumbing.
Some registrars (for example register-task-workflow-routes.ts) also take a narrow dependency-injection object for non-context helpers that must stay source-of-truth in routes.ts (git helpers, background refresh helpers, multer upload middleware). This avoids helper duplication while preserving runtime parity.
The context provides core cross-cutting plumbing:
- Request/project scoping:
getProjectIdFromRequest,getScopedStore,getProjectContext- These are also exported from
context.tsas canonical helpers for future extraction tasks.
- These are also exported from
- Engine-aware fallback behavior for project-bound and root-store APIs
- Runtime loggers and diagnostics emitters (
runtimeLogger,planningLogger,chatLogger) - Proxy/auth/audit helpers (
emitRemoteRouteDiagnostic,emitAuthSyncAuditLog) - Automation/routine resolvers and scope parsing helpers
- Shared error normalization (
rethrowAsApiError)
Registrar module map
register-settings-memory-routes.ts— settings APIs and memory backend/file/insight routes (excluding node-to-node sync endpoints)register-project-routes.ts—/projectsCRUD +/projects/across-nodes,/projects/detect, health/config/pause/resume routesregister-node-routes.ts—/nodesCRUD + operational endpoints (/health-check,/metrics,/version,/sync-plugins,/compatibility)register-settings-sync-routes.ts— node settings/auth sync routes (/nodes/:id/settings*,/nodes/:id/auth/sync)register-mesh-routes.ts— mesh topology routes (/mesh/state,/mesh/sync)register-discovery-routes.ts— discovery routes (/discovery/status|start|stop|nodes|connect) withoptions?.centralCorereuseregister-settings-sync-inbound-routes.ts— inbound sync/auth endpoints (/settings/sync-receive,/settings/auth-receive,/settings/auth-export)register-settings-sync-helpers.ts— shared sync-domain helpers (fetchFromRemoteNode,readStoredAuthProvidersFromDisk)register-task-workflow-routes.ts— task/workflow domain (/tasks*,/documents, task comments/docs/checkout/spec/attachments, PR+issue status, task lifecycle/workflow endpoints)register-planning-subtask-routes.ts— planning sessions and subtask breakdown routesregister-chat-routes.ts— chat session/list/mutation/stream routesregister-messaging-scripts.ts— scripts API and mailbox/message routesregister-git-github.ts— git/GitHub workflows and related helpers- Git plumbing routes:
/git/remotes*,/git/status,/git/commits*,/git/branches*,/git/worktrees,/git/fetch|pull|push,/git/stashes*,/git/diff*,/git/changes,/git/stage|unstage|commit|discard - GitHub import/integration routes:
/github/issues/*,/github/pulls/*,/github/webhooks,/github/batch/status(includes shared batch-import rate limiter state + reset export) - Task-scoped GitHub routes:
/tasks/:id/pr/*and/tasks/:id/issue/*status/refresh/create flows
- Git plumbing routes:
register-model-routes.ts—/modelsendpoint, favorites projection, anduseClaudeClifiltering forpi-claude-clientriesregister-auth-routes.ts— auth/provider domain (/auth/status,/auth/login,/auth/logout,/auth/api-key,/auth/claude-cli,/providers/claude-cli/status)register-usage-routes.ts—/usageendpoint withfetchAllProviderUsage(options?.authStorage)integrationregister-files-terminal-workspaces.ts— infrastructure aggregator for file/workspace + session-diff + terminal routes- Calls
register-session-diff-routes.tsfirst (session changed files + task diff endpoints) - Calls
register-file-workspace-routes.tssecond (task/workspace file browsing and file operations) - Calls
register-terminal-routes.tslast (terminal command/session + PTY lifecycle endpoints)
- Calls
register-file-workspace-routes.ts— task/workspace file domain:- Task files:
/tasks/:id/files,/tasks/:id/files/{*filepath}(read/write) - Workspace discovery/files:
/workspaces,/files,/files/markdown-list,/files/search,/files/{*filepath} - File operations:
/files/{*filepath}/copy|move|delete|rename,/files/{*filepath}/download,/files/{*filepath}/download-zip - Generic wildcard write:
/files/{*filepath}(must remain after operation routes) - Project markdown search:
/project-files/md
- Task files:
register-session-diff-routes.ts— task session/diff domain:- Session changed-file list:
/tasks/:id/session-files - Aggregate task diff:
/tasks/:id/diff - Per-file diffs:
/tasks/:id/file-diffs - Caches: module-level
sessionFilesCacheandfileDiffsCache(10-second TTL)
- Session changed-file list:
resolve-diff-base.ts— shared git diff-base utilities:runGitCommand(args, cwd, timeoutMs)resolveDiffBase(task, cwd)+ResolveDiffBaseTaskInputtype
register-terminal-routes.ts— terminal execution and PTY endpoints:- Command execution + streaming:
/terminal/exec,/terminal/sessions/:id,/terminal/sessions/:id/stream,/terminal/sessions/:id/kill - PTY lifecycle:
/terminal/sessions(create/list) and/terminal/sessions/:id(delete)
- Command execution + streaming:
register-agent-core-routes.ts— core agent CRUD, lookups, stats/org-tree, hierarchy aliases (/agents/:id/children|employees)register-agent-runtime-routes.ts— agent runtime/control-plane, heartbeats/runs, access/permissions, soul/memory, revisions/budget/keys, task/inbox surfacesregister-agent-reflection-rating-routes.ts— reflection/performance/context endpoints and ratings APIsregister-agent-import-export-generation-routes.ts— agent import/export, companies catalog, and/agents/generate/*session/spec lifecycleregister-agent-skills-routes.ts— skills discovery/content/execution/catalog endpoints coupled to agent capability flowregister-plugins-automation.ts— plugin CRUD, automation, routines/webhooksregister-proxy-routes.ts— remote-node proxy forwarding and SSE proxy routes- Injected dependencies:
{ store, runtimeLogger } - Endpoint inventory (must remain in this order):
GET /proxy/:nodeId/healthGET /proxy/:nodeId/projectsGET /proxy/:nodeId/tasksGET /proxy/:nodeId/project-healthGET /proxy/:nodeId/events(SSE pass-through, 30s timeout, client-disconnect cleanup)ALL /proxy/:nodeId/{*splat}(generic wildcard forwarder)
- Shared diagnostics: imports
emitRemoteRouteDiagnosticandclassifyRemoteRouteErrorfromroutes/context.tsso proxy and non-proxy registrars (for example mesh/sync routes) keep one diagnostic classification contract.
- Injected dependencies:
createApiRoutes mount sequence (current)
createApiRoutes() mounts registrars in this precedence-sensitive order:
registerSettingsMemoryRoutes(...)registerTaskWorkflowRoutes(...)registerPlanningSubtaskRoutes(...)registerChatRoutes(...)registerMessagingScriptRoutes(...)registerGitGitHubRoutes(...)registerFilesTerminalWorkspaceRoutes(...)registerAgentsProjectsNodesRoutes(...)registerPluginsAutomationRoutes(...)- (later)
registerAgentSkillsRoutes(...) - (last)
registerProxyRoutes(...)
Compatibility re-exports that must remain on routes.ts for tests and existing importers:
resolveDiffBase+ResolveDiffBaseTaskInput(fromresolve-diff-base.ts)__resetBatchImportRateLimiter(fromregister-git-github.ts)__setCreateFnAgentForRefine(defined inroutes.ts)
Ordering rules (critical)
Express matches in registration order. Keep registrar and in-registrar route ordering stable:
- Specific operation routes before generic parameterized routes (
/runs,/runs/:id,/copy,/deletebefore/:idstyle handlers) - Specific operation routes before wildcard paths (
/files/{*filepath}/copy|move|delete|rename|download|download-zipbeforePOST /files/{*filepath})- Why: Express route matching is first-win. If the wildcard write route is registered first, paths like
/files/somefolder/deletewill be treated as file writes instead of delete operations.
- Why: Express route matching is first-win. If the wildcard write route is registered first, paths like
- Do not move proxy/script/message/file wildcards ahead of specific routes
- For proxy routes specifically, keep all explicit
GET /proxy/:nodeId/*handlers ahead ofALL /proxy/:nodeId/{*splat}and keep proxy registration last increateApiRoutes().
- For proxy routes specifically, keep all explicit
- Project/node/sync/discovery ordering constraints must stay intact:
/projects/across-nodesand/projects/detectmust be registered before/projects/:id/nodes/:id/settingsmust be registered before/nodes/:id/settings/push|pull|sync-statusand before/nodes/:id/auth/sync/mesh/statemust be registered before/mesh/sync- Discovery routes stay grouped after mesh routes
- Inbound
/settings/sync-receive|auth-receive|auth-exportroutes mount after discovery routes
- Auth/model/usage ordering constraints must stay intact:
- Keep
/modelsregistration before auth-dependent picker/settings flows that rely on consistent model filtering - Keep auth registrar routes grouped as currently mounted (status/diagnostic + mutation endpoints) so no wildcard handler can shadow
/providers/claude-cli/status - Keep
/usagemounted as a standalone registrar route (not under auth paths) with unchanged error mapping semantics
- Keep
- Agent ordering constraints must stay intact:
/agents/stats,/agents/org-tree,/agents/resolve/:shortnamebefore/agents/:id/agents/:id/runs/stopbefore/agents/:id/runs/:runId/agents/:id/reflections/latestbefore/agents/:id/reflections
If adding a new endpoint, place it in the domain registrar and verify it does not shadow existing handlers.
Integrated routers
Integrated routers are mounted through register-integrated-routers.ts and intentionally called from routes.ts at precedence-sensitive points:
registerIntegratedRouters(...)mounts:createMissionRouter→/api/missionscreateRoadmapRouter→/api/roadmapscreateInsightsRouter→/api/insights
registerIntegratedDevServerRouter(...)mounts:createDevServerRouter→/api/dev-server
Keep these calls in their current positions inside createApiRoutes() unless an explicit route-ordering migration is planned and regression-tested.