When the dashboard TUI collapses to single-pane mode (narrow width or
short terminal), show a horizontal log strip at the bottom of the screen
while the active section renders on top. The split is content-driven:
the top pane gets exactly the rows it needs to render its section
without truncating (computed from SystemPanel's chip wrap at the current
width, or each panel's known row count for Stats/Utilities/Settings),
and the log strip absorbs every remaining row to maximize log
visibility. Down-arrow shifts sub-focus into the log strip with the
same key bindings as the dedicated Logs section (j/k, Home/G, Enter to
expand, w to wrap, c to copy, f to filter, mouse wheel). Up-arrow at
the top of the strip or Esc returns focus to the main pane.
Right/Left/Tab continue to cycle sections, so the dedicated full-screen
Logs view is unchanged. The split auto-disables if the log strip would
get fewer than 6 rows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P0 — parsePorcelainZ rename/copy handling
Git's -z porcelain emits `R <new>\0<old>\0` for renames (and
C for copies). The naive split-and-slice treated <old> as an
independent dirty path, which made runObservedDestructiveSyncOp
warn about phantom "cleared paths" whenever a rename was in
flight. Now we detect R/C status and skip the trailing entry.
P1 — race-rescue loop unstages between attempts
`git stash create` snapshots the index without clearing it, so
iteration 2's `git add -A` would re-stage atop iteration 1's
leftovers. Tree differences inside the loop then reflected stale
staging rather than genuine new writes. Added a `git reset` at
the top of each iteration so every attempt starts from a clean
index baseline.
P1 — writeActiveMergerStatus is now atomic
Switched from in-place writeFileSync to temp-file + renameSync.
POSIX guarantees rename atomicity on the same filesystem, so a
reader can no longer catch the file mid-flush and return a
false-negative "no merger active" advisory.
P2 — Step regex em-dash clarity
`[—\-:]` is functionally fine but obscures intent; switched to
`(?:—|-|:)` so the em-dash branch is obvious. Added a test case
for the em-dash separator.
New tests:
- parse-porcelain-z.test.ts (8 cases including renames + copies)
- em-dash case added to derive-subject-summary.test.ts
247/247 merger-suite tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The race-rescue loop was firing on every merger run because
`git add -A && git stash create` does not clean the working tree —
files stay dirty post-stash, so a subsequent `snapshotDirtyFiles` saw
the SAME paths the primary stash had just captured and stashed them
again, producing identical-tree race-rescue duplicates (visible in
git stash list as `fusion-merger-autostash:FN-XXXX:race-rescue-0`
sitting next to its identical `fusion-merger-autostash:FN-XXXX:`).
Fix: list the path set captured by the primary stash via
`git stash show --name-only`, and only rescue paths in the current
dirty snapshot that are NOT in that set — those are genuine
late-dirty writes from concurrent dev edits or interleaved ops.
Also drop any rescue whose tree-SHA exactly equals the primary,
as a defensive belt-and-braces.
Existing duplicate race-rescue stashes are harmless (identical
content to their primaries) and can be dropped manually.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- writeActiveMergerStatus: writes .git/.fusion-merger-active.json
(taskId, pid, hostname, startedAt) at merge entry, deleted in finally.
Not a lock — purely informational so dashboards / status lines /
pre-Edit hooks can warn devs that rootDir is volatile during the run.
readActiveMergerStatus(rootDir) is exported for consumers.
- runObservedDestructiveSyncOp: snapshot-before/after wrapper around
destructive rootDir ops that are *supposed* to preserve unrelated
working-tree edits. resetMergeWithWarn now uses it — any future
silent wipe of dirty paths surfaces as an actionable warning instead
of going unnoticed. Not applied to the autostash's own reset
--hard / clean -fd; those are intentionally destructive and already
protected by the race-rescue stash.
- Race-rescue stashes from stashUnrelatedRootDirChanges are now
attached to the AutostashHandle and surfaced via store.logEntry so
the recovery command lands on the task feed instead of only
mergerLog.warn.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- deriveDeterministicSubjectSummary now picks the lowest-numbered
`complete Step N` headline (or the oldest commit) instead of the most
recent commit, so trailing quality-gate revisions stop hijacking the
squash-merge subject (FN-3617 landed as "align mailbox modal css..."
when 4 of 5 commits were the actual Claude OAuth fix).
- AI subject + body system prompts in ai-summarize.ts now weight by
commit theme rather than file size, so a small token cleanup that
touches a large CSS file no longer dominates the summary.
- stashUnrelatedRootDirChanges adds a bounded re-snapshot loop after
the primary stash is persisted but before \`git reset --hard\`. Any
late-dirty paths (concurrent dev edits during a long merger run,
parallel merger runs racing on rootDir, late test/build artifacts)
get captured in labeled \`race-rescue-N\` stashes recoverable from
\`git stash list\`, instead of being wiped by the destructive reset.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The monolithic `TaskDetailModal` test suite (6,745 lines) was split into six focused test files covering attachments/tabs, definition/actions, inline editing/integrations, models/progress/workflow, rendering, and responsive/dependencies, with a shared test helpers module added for common utilities.
Fusion-Task-Id: FN-3591
Merged FN-3602: Compact tool-call summaries in the ChatView with responsive mobile layout improvements, including new regression tests and documentation in the dashboard guide.
Fusion-Task-Id: FN-3602
Merges five commits implementing centralized runtime skill forwarding that preserves Fusion context across the Hermes runtime layer. The engine's `agent-runtime` and `agent-session-helpers` were updated to forward skills at runtime, with `runtime-adapter.ts` and its types extended to carry context.
Fusion-Task-Id: FN-3612
Two cuts to wasted work in the merge verification loop:
1. After the in-merge fix agent runs, fingerprint the working tree
(`git diff HEAD` + `git status --porcelain`, sha256). If the post-fix
fingerprint matches pre-fix and is non-empty, the agent didn't actually
change anything — re-running the same failing command can only yield
the same failure, so log and report the attempt as unsuccessful without
paying the test/build cost. Empty fingerprints (snapshot tooling failed)
fall through to the existing re-run path so we never silently swallow a
real fix.
2. Inside `syncDependenciesForMerge`, hash the active lockfile and compare
against `node_modules/.fusion-install-marker` (written after each
successful install). When they match, skip `pnpm install
--frozen-lockfile` even if `package.json` is staged. Covers the common
case where `package.json` changes but the lockfile doesn't, and
amortizes install across auto-recovery re-enqueues that hit the same
worktree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /_perf/dashboard-load server route no longer exists, so every
reportDashboardPerf() call was a silently-swallowed 404. Remove the helper
in legacy.ts plus its five call sites in App.tsx / useProjects.ts (the
companion console.log lines stay), and drop the dashboard-perf.log entry
from the test-isolation runtime ignore list since nothing writes that file
anymore.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live fusion instances running on a shared HOME write to tasks/, messages/,
memory-insights.md, test-cache.json, HEARTBEAT.md, kb.db.backup-*, and
fusion.db.pre-* snapshots; none were in the runtime ignore list, so the
script flagged them as test-driven mutations and failed merge build
verification. Recursion in collectFusionSignature only filters via top-level
matches, so adding these top-level patterns short-circuits descent into
live-app dirs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This merge introduces eval score categorization with a new `eval-scoring.ts` module (FN-3601, FN-3390), hardened bundled plugin entry resolution to prioritize workspace source over installed copies (FN-3590), and documented reply-link threading behavior in the mailbox (FN-3598). It also adds mobile
Fusion-Task-Id: FN-3590
Local `pnpm test:isolated` was failing because a concurrently-running
fusion app continually mutates `~/.fusion` (databases, agent sessions,
memory, automations, plugins, logs). Filter those runtime-owned paths
from the protected-dir signature, widen the baseline-stability sampling
window, and re-sample on suspected violations so transient app activity
doesn't masquerade as test pollution.
Tests still cannot legitimately write into these paths — they're skipped
because the *running app* is expected to.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This merge restores the canonical agent lifecycle with termination scoped at the run level (FN-3580, 4 steps), adds sender-side wake recipient override for messages, and introduces test isolation CI enforcement with a stuck-requeue race fix. UI changes remove terminated-agent indicators from AgentDe
Fusion-Task-Id: FN-3580
Documents the live reviewer override behavior in the settings reference and task management guides, adding two lines to each file for a total of 4 lines of documentation.
Fusion-Task-Id: FN-3595
This merge lands five FN-3593 commits establishing a test isolation contract with a new `scripts/check-test-isolation.mjs` guard that scans for accidental `beforeEach`/`afterEach`/`beforeAll`/`afterAll` in setup helpers, plus per-package `setup-test-isolation.ts` bootstraps that canonicalize the pat
Fusion-Task-Id: FN-3593
Senders can now force the recipient agent to wake on receipt regardless
of the recipient's `messageResponseMode`. Surfaced as a "Wake recipient
immediately" checkbox in MessageComposer and as a `wake_recipient`
boolean param on the `fn_send_message` agent tool. Carried as
`metadata.wakeRecipient: true` on the message; the heartbeat hook
treats forced wakes as `message_received_urgent` in the wake delta so
agents can distinguish them from normal `messageResponseMode: immediate`
wakes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When SelfHealingManager.recoverCompletedTasks moved a task from
in-progress to in-review, the executor's stuck-kill cleanup running in
execute()'s finally block could fire 20s later, see a stale captured
task.column = "in-progress", and overwrite the recovery by tearing down
the worktree and moving the task back to todo with all step progress
reset. Both the outer-finally and step-session requeue blocks (and the
force-requeue setTimeout in markStuckAborted) now re-read the latest
column and skip cleanup entirely if the task has moved past
in-progress/todo.
Adds a new preserveProgressOnStuckRequeue setting (default: true,
toggle in Settings near the Stuck Task Timeout) so stuck-requeue passes
{ preserveProgress: true } to moveTask. Completed step statuses now
survive the bounce so the agent resumes from where it left off instead
of restarting every step from pending.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
POST /api/agents/:id/runs previously awaited resolvedMonitor.executeHeartbeat
end-to-end before sending the response. For real provider runs that take
tens of seconds to minutes, Safari (and intermediate proxies) drop the
client socket and the dashboard surfaces "Failed to start heartbeat run:
load failed" — the run is actually in flight, but the toast suggests it
failed to start.
The route now kicks off executeHeartbeat in the background, polls briefly
for the active-run record (created synchronously inside executeHeartbeat
→ startRun), and returns 201 with that record. Synchronous failures of
executeHeartbeat are still surfaced to the client; background failures
are logged via runtimeLogger.child("heartbeat"). The 409 active-run
conflict contract is preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Overview dropdown previously rendered "X active · Y running" where
both X (stats.activeCount) and Y (activeAgents.length) counted agents
whose state was either "active" or "running" — so an agent that was
merely enabled but idle would still inflate the "running" tally. The
label now counts each state distinctly so "running" only reflects
agents that are mid-heartbeat. Adds AgentsOverviewBar.test.tsx to lock
in the new contract.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the dashboard user browses another agent's mailbox (e.g. the CEO's
inbox), opening a message no longer triggers POST /messages/:id/read.
The previous behavior silently consumed the agent's unread state, so the
agent's heartbeat never surfaced the message and fn_read_messages (which
defaults to unread_only=true) returned nothing. Auto-mark-read now only
fires on the dashboard user's own inbox tab. Adds regression tests in
MailboxView.test.tsx and MailboxModal.test.tsx.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The InProcessRuntime constructed its AgentStore with only `rootDir`, leaving
task-claim/checkout/release operations unconfigured. As a result, the
heartbeat auto-claim scan logged "TaskStore not configured for task-claim
operations" whenever a relevant todo was found. Pass the runtime's TaskStore
through to the AgentStore so claimTaskForAgent succeeds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This merge introduces a memory file markdown preview feature (FN-3584) with corresponding documentation, refines the AgentDetailView and AgentLogViewer components in the dashboard, and adds defensive collision handling for worktree operations during manual task moves (FN-3583).
Fusion-Task-Id: FN-3584
Two related bugs let two in-progress tasks share a single
.worktrees/<name> directory:
1. The dashboard POST /tasks/:id/move route promoted tasks to
in-progress without allocating a fresh worktree path, so a queued
task carrying a stale worktree field from a prior preserveResumeState
requeue could land in-progress on a directory already held by another
active task.
2. moveTask({preserveResumeState:true}) kept the worktree pointer on
requeue. When the on-disk checkout was later removed or reassigned,
the next dispatch collided with a worktree the scheduler had handed
to another task.
moveTask now releases the worktree pointer on every reopen-to-todo hop
(branch is kept so committed progress survives via git worktree add
<path> <branch>). A new preserveWorktree option opts internal bounces
out of the release. moveTask also accepts an allocateWorktree callback
that runs under a new cross-task allocation lock in TaskStore, so two
concurrent moves cannot pick the same name from a stale snapshot. Both
the manual-move route and the scheduler dispatch path flow through the
allocator and share the lock.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Convert the dynamic await import("./agent-session-helpers.js") and
await import("./session-skill-context.js") calls inside the heartbeat
executor to static top-level imports, matching the rationale of
38933c770 (which already made the sibling pi.js import static).
This surfaces ERR_MODULE_NOT_FOUND at engine load time rather than
mid-heartbeat when a worktree is on a branch missing the helpers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This merge adds a complete plugin management system (FN-3565) with CLI commands, a loader, runner, and dashboard routes, along with project-scoped auth storage (FN-3544), native shell connection support for mobile (FN-3400) spanning onboarding, connection manager, and remote desktop handoff, and ref
Fusion-Task-Id: FN-3400
This merge adds a complete plugin management system to Fusion: a new `fn plugin` CLI command for installing/removing plugins, a plugin loader in core, a plugin runner in engine, and dashboard routes for plugin management UI, along with a plugin management guide in docs. It also documents task evalua
Fusion-Task-Id: FN-3565
- Promote the sidebar "Plan New Mission" CTA to a btn-primary (matching the
chat sidebar's "New Chat") and drop the dashed icon buttons; full-width
progress bar and Activity row each get their own line in the card.
- Auto-select the first mission in the inline desktop view so users land
in detail rather than the empty placeholder. Skipped in mobile and the
standalone modal so existing flows and unit tests stay intact.
- Replace the bare "No missions yet" line with a richer empty state that
explains what missions are and offers an inline Plan New Mission CTA.
- Guard loadMissionDetail against malformed responses (missing milestones)
so racing fetch fallbacks don't crash the detail render.
- Update three MissionManager tests to match the new copy/structure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TriageProcessor.stop() previously only halted the polling loop, so
in-flight specify sessions and their reviewer subagents kept streaming
past shutdown. Extracted the existing global-pause teardown into
abortAndDisposeActiveSessions() and call it from stop() too.
aiMergeTask creates three sessions during a merge — autostash resolver,
in-merge verification fix agent, and pull-rebase conflict resolver — but
only the autostash one was registered via onSession. The other two are
now registered (with onSession threaded through pushToRemoteAfterMerge
into the rebase resolver chain), so ProjectEngine.stop() actually
disposes whichever merger session is running when shutdown lands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Recovers cleanups from stash@{2}/stash@{3} (FN-3530 merger autostashes)
that the merger never restored: 4 dead `[data-state="terminated"]`
selectors in AgentListModal.css, 3 CSS-class assertions in
agent-css-classes.test.ts targeting classes the runtime no longer emits,
and a `state: "terminated"` fixture in routes-agents.test.ts now flipped
to `paused` so the "invalid state transitions" test exercises a real
rejection (paused→paused is not in AGENT_VALID_TRANSITIONS).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Stack mission cards vertically inside the sidebar so the title gets
the full card width; action buttons drop to their own row below.
- Move the Activity timestamp out of the cramped stats row onto its
own line.
- Replace the icon-only Sparkles button in the sidebar header with a
full-width centered "Plan New Mission" button (icon + text). Mobile
footer button uses the same label.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Make the mission split sidebar drag-resizable (220–560px, persisted) so
long mission titles aren't trapped behind a fixed-width column.
- Move card tags (autopilot/health/status) to a row below the title and
drop the overflowing "Active: …" line.
- Collapse mission creation to a single AI-driven entry point: rename
Sparkles to "Create New Mission" and remove the manual "+" button.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Creating a new terminal tab caused every pty data chunk (including
keystroke echo) to render twice in xterm. The connect-effect's
`contextChanged` dep flips true→false in the same render cycle as
the new connection: the effect re-runs, React calls cleanup which
closed the still-CONNECTING WS without nulling its handlers, then
connect() opens a fresh WS. The ghost socket's onmessage continued
firing on the shared `onDataCallbacksRef` Set, delivering each chunk
twice (and producing the "WebSocket is closed before the connection
is established" warning). Null onopen/onmessage/onclose/onerror in
both cleanup() and connect()'s pre-close branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merges FN-3534 to surface task provenance in CLI extension outputs (task list/show) with corresponding tests and a changeset for the published `@runfusion/fusion` package. Also includes FN-3297 test coverage for incomplete runtime distribution trees in the test-artifacts script.
Fusion-Task-Id: FN-3534
Recovery path now calls completeRun(terminated) so the canonical agent-state
transition runs, and reconcileOrphanedRunningAgents both catches stale-heartbeat
cases and runs every poll so pre-existing stuck rows self-heal post-upgrade.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add task provenance fields to extension task list/show outputs for better source visibility
- Expand CLI extension tests to cover provenance rendering in task list and task detail responses
- Add a changeset for @runfusion/fusion documenting the provenance output update
- Harden ensure-test-artifacts script and tests to cover incomplete runtime dist tree scenarios
Fusion-Task-Id: FN-3534
Surfaces Import alongside "New Agent" in the agents header and removes
the duplicate buttons from individual agent detail pages and the
controls panel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Permanent agents now run heartbeats regardless of bound-task block state.
The prior queued+blockedBy early-exit and its state-tracking machinery are
removed; HEARTBEAT_SYSTEM_PROMPT is rewritten to scope heartbeats to
ambient coordination (messaging, memory, finding work, delegation,
surfacing/chasing blockers, status). Task body work continues via the
executor path. Ephemeral agents are unchanged.
New allowParallelExecution flag (default true, permanent agents only) on
AgentHeartbeatConfig. When false, heartbeat and executor paths serialize
symmetrically: a heartbeat will not start while the agent's bound task
has an active executor session, and an executor session will not start
while the agent has an active heartbeat run. Either side re-dispatches
the other's deferred work on completion. UI toggle surfaces in the
agent's Heartbeat Settings tab.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a "Tools: On/Off" toggle next to the existing markdown toggle in
AgentLogViewer (used by both agent logs and task agent logs). When tool
output is off, tool/tool_result/tool_error entries are filtered before
grouping so only agent text and thinking render. Both toggles persist
globally across sessions via localStorage (fn-agent-log-markdown,
fn-agent-log-tool-output).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a permanent agent has allowParallelExecution=false, TaskExecutor.execute()
defers if the agent has an active heartbeat run, and HeartbeatScheduler defers
a heartbeat if the agent's bound task has an active executor session. Each side
re-dispatches the other's deferred work on completion via resumeTaskForAgent
and the in-process runtime's onRunCompleted hook.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When rootDir is the developer's primary checkout, the merger stashes
uncommitted edits before its hard resets and applies them back at the
end. Previously a pop conflict logged a single warning and silently
left the stash in place — a subsequent merge would push another
autostash on top, burying the first. Recent FN-3299 work was lost this
way and surfaced two side-by-side fusion-merger-autostash entries in
the local stash list.
Three changes:
- AI auto-resolve on apply conflict. The new
runAiAgentForAutostashConflict spawns the same createResolvedAgentSession
path as the in-merge fix-agent, instructs it to clear conflict markers
in place without committing, and verifies markers are gone post-run.
On verified success the stash is dropped; on any failure or remaining
markers the stash is left intact for manual recovery.
- Outcome surfaced via new MergeResult.autostash (AutostashOutcome)
field so dashboard / CLI / daemon can show developers whether their
work was reapplied cleanly, AI-resolved, or needs manual recovery.
- Deterministic stash identity. Replaced `git stash push` + label-grep
(which races against concurrent stashing tools) with `git stash create`
+ `git stash store`, capturing SHA atomically with snapshot creation
and using it for apply / drop. Untracked files captured via `git add
-A` before create; cleanup via `git reset --hard` + `git clean -fd`.
Also surfaces orphaned `fusion-merger-autostash:*` entries from prior
runs at merge entry, so they can no longer be silently buried.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hermes / OpenClaw / Paperclip runtime cards in Settings now lazily
register themselves on first Save instead of failing with `Plugin
"fusion-plugin-...-runtime" not found`. The CLI also bundles each
runtime plugin (with @fusion/plugin-sdk inlined via esbuild) into
dist/plugins/<id>/bundled.js so npm/npx-installed Fusion can load them
without the workspace SDK dependency.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the engine boots, if an agent has the new
runMissedHeartbeatOnStartup flag enabled and lastHeartbeatAt is older
than its interval, fire one catch-up heartbeat through the existing
executeHeartbeat path. Default is off, so existing agents are
unchanged. Toggle exposed in the agent's Heartbeat Settings tab.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Only running shows green and error shows red; idle/active/paused share
the neutral gray border and badge across the agent list, board, and
org-chart views.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops "terminated" from AGENT_STATES. The agent lifecycle now runs through
idle | active | running | paused | error. paused (carrying a pauseReason)
absorbs every former terminated use case — manual stop, heartbeat run
termination, spawned-child cleanup. Run status (agentRuns.status) is
unchanged: "terminated" stays a valid run-status value.
AGENT_VALID_TRANSITIONS allows direct any→idle transitions so resetAgent
no longer needs the intermediate hop.
Stack-wide:
- core/agent-store: lastError clearing + resetAgent simplified.
- engine/agent-heartbeat, executor, in-process-runtime: terminated state
writes → paused; halt-state listener fires on paused/error.
- dashboard: AgentsView/AgentListModal/AgentDetailView lose the Terminated
badge/option/state-block; agent pickers no longer filter terminated;
agentHealth drops the Terminated branch; routes/state cast widened to
the new AgentState union.
Tests across core and engine updated to assert paused for AgentState and
left "terminated" intact for run-status assertions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>