- Render a compact token usage indicator in TaskCard footer with accessible labeling and token-aware styling
- Track token usage fields in the TaskCard memo comparator and expose a comparator test helper for regression coverage
- Add TaskCard tests for token usage rendering behavior and comparator invalidation on token usage updates
- Configure runtime plugin Vitest setups with an @fusion/engine source alias for reliable workspace test resolution
- Keep restart integration child_process spawn mocking aligned with execSync-driven merge verification behavior
Three fixes for the worktree-overflow / stuck-task incident:
1. Cap deterministic-verification-failure bounces (fix#2)
Auto-merge previously bounced an in-review task back to in-progress
on every verification failure with no upper bound. A single flaky test
could keep a task ping-ponging in-review→in-progress forever, holding
its worktree and consuming agent slots. Adds verificationFailureCount
on Task (DB migration v48), increments on each bounce, and after 3
failures marks the task failed and creates a follow-up triage task
so a fresh agent can investigate the underlying flake instead of
re-running the same fix loop.
2. Reap unregistered orphan worktree dirs even when recycle is on (fix#3)
cleanupOrphans previously bailed out entirely when recycleWorktrees
was true, leaving stale dirs (clear-hawk-broken, *-bak, leftover
crash debris) on disk forever. New reapUnregisteredOrphans pass
removes only directories that aren't registered git worktrees, so
the recycle pool keeps its warm worktrees but the trash gets cleared.
3. Idempotence guard on activity-log listener wiring (fix#6)
setupActivityLogListeners() was registering handlers on every call.
When init() ran twice, every task:created / task:moved event wrote
N rows to activityLog, producing the duplicate entries visible in
the DB. Added activityListenersWired flag so repeated calls no-op.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
47514942 switched merger verification from exec to spawn-based
execWithProcessGroup, but restart.integration.test.ts only mocked
execSync/exec. spawn() returned undefined, so the merger crashed before
running the test command and any in-review merge test that hit the
verification path failed with VerificationError.
Adds a spawn mock that funnels through the existing execSync mock so a
single mockedExecSync.mockImplementation continues to control both git
calls and verification command outcomes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hitting Stop (globalPause) disposed the AI merge agent session but left
the spawned `pnpm test` / `pnpm build` child processes running until
they finished naturally. With recurring flaky-test loops at Step 5,
that meant Stop had no visible effect — new test runs kept piling up
across multiple worktrees.
Two gaps:
- project-engine.ts onGlobalPause never called mergeAbortController.abort(),
so subsequent verification commands (gated by the signal) weren't cancelled.
- merger.ts execWithProcessGroup only listened to its own internal
timeout — passing an AbortSignal had no effect on the in-flight
child process group.
Fix: abort the controller on global pause, and have execWithProcessGroup
SIGTERM/SIGKILL the detached process group when its signal aborts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 5-minute exec timeout only killed the immediate shell, leaving
vitest/pnpm worker trees alive. Across retries these accumulated and
thrashed the host, starving the engine and TUI. Switch verification to
spawn-based runner with detached process group so timeouts SIGTERM the
whole tree (SIGKILL after 5s grace), and bump the wallclock to 10m for
larger workspaces. Stream-truncate output instead of relying on ENOBUFS.
Also fix two flaky/race-prone dashboard tests that were red on main and
blocking every in-review task at merge verification.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- fix(FN-2613): restore green CI after static engine import changes
- test(FN-2613): complete Step 3 — update mobile and standalone CSS assertions
- feat(FN-2613): complete Step 2 — move standalone token override to global styles
The dashboard modules used a variable-specifier dynamic import
(`const m = "@fusion/engine"; await import(m)`) to defeat bundler static
analysis. tsup honored that and left the dynamic import in dist/bin.js,
so the published `@runfusion/fusion` package failed at runtime with
"createFnAgent2 is not a function" — `@fusion/engine` isn't on npm and
the silent catch set the binding to undefined. Replaces the trick with
static imports across planning, chat, subtask-breakdown, mission-interview,
agent-generation, ai-refine, roadmap-suggestions, milestone-slice-interview,
and routes. Core can't statically import engine (cycle), so it now exposes
setCreateFnAgent and engine wires itself in at module load. Documents the
pattern in AGENTS.md.
FixesRunfusion/Fusion#9.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove the standalone remoteEnabled setting from CLI, core settings defaults/types, and dashboard settings APIs/UI
- Treat remote access as enabled when an active provider is selected and that provider is configured as enabled
- Update remote auth and engine lifecycle checks to gate on provider activation instead of a global flag
- Adjust tests and add a changeset documenting the remote access configuration simplification
- Add regression tests across CLI, core, dashboard, and engine for remote access auth, settings parity, and serve/TUI callback wiring
- Expand dashboard route and modal coverage for remote settings/auth flows including node environment behaviors
- Redact provider-switch failure details in tunnel process manager to avoid leaking sensitive provider diagnostics
- Update route registration and engine lifecycle tests to lock in remote-access behavior under real execution paths
- Add ProjectEngine restore lifecycle core to perform safe restarts and surface detailed restore state transitions
- Expose restore diagnostics through remote-access status types and settings/memory route context, including legacy API mapping updates
- Add comprehensive regression coverage for restore lifecycle behavior in engine and dashboard headless remote-access tests
- Document the restore lifecycle contract in architecture/settings docs and include a patch changeset for @runfusion/fusion
- Add remote-access contracts, provider adapters, and a tunnel process manager with lifecycle handling
- Wire tunnel manager into ProjectEngine startup/shutdown flow and export new remote-access modules
- Update settings modal UX for remote auth URLs, including wrapping and related UI test coverage
- Document tunnel manager behavior and remote settings sync details in architecture, CLI, and settings docs
Triage with claude-sonnet-4-6 via pi-claude-cli kept looping on
fn_review_spec calls that were rejected by pi's validator with
"root: must be object". Parameterless MCP tools (schema
{type:"object", properties:{}}) emit zero input_json_delta events,
so partialJson stayed "" and the catch fell through to
finalArgs = "" — a string, which TypeBox's Type.Object({}) rightly
refuses. Default empty partialJson to {} so the call lands.
Also:
- Add a 2-step reminder loop in triage before swapping to the
fallback planning model — primary models that wrote PROMPT.md
but forgot fn_review_spec recover from a nudge, no need to pay
the cold-start tax of a new triage on a different model.
- Inject @runfusion/fusion's own pi extension into dashboard/
daemon/serve sessions and propagate the path to createFnAgent
via setHostExtensionPaths so fn_* tools register globally
without requiring `pi install npm:@runfusion/fusion`.
- Drop the "historical" qualifier from replayed tool labels —
Claude was reading "TOOL RESULT (historical Read):" as
"previous session, ignore" and looping on verification.
- Remove subprocess-lifecycle stderr debug logs that landed for
hang diagnosis — root cause is fixed, the noise can go.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add merger abort primitives and track active merge runs for coordinated cancellation
- Abort in-flight merges during engine shutdown and propagate AbortError through fallback catch paths
- Honor abort signals before commit, push, and dependency sync to prevent post-cancel side effects
- Expand merger and project-engine tests to cover abort propagation and merge-abort-on-stop behavior
- Preserve overdue nextRunAt when schedule updates only touch non-cadence fields
- Recompute nextRunAt only when cadence changes, schedules are re-enabled, or nextRunAt is missing
- Sync memory dreams automation during ProjectEngine startup before CronRunner begins ticking
- Add core/engine regression coverage and a patch changeset for @runfusion/fusion release notes
pi-coding-agent 0.70's createCodingTools preset only includes read/bash/edit/write,
but pi-claude-cli's tool-mapping translates Claude's Glob→find and Grep→grep.
Triage sessions running through Claude CLI hit "Tool find not found" the moment
the model called Glob and looped on the error.
Compose the built-in set explicitly so every tool referenced by tool-mapping.ts
is registered. Read-only sessions also gain ls (was silently dropped earlier
when readonly was redefined upstream).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add overlap-ignore path validation and typed settings support in core schema
- Apply overlap ignore paths in scheduler overlap detection with dedicated engine tests
- Add Settings modal UI and routes handling for overlap ignore paths including path-picker feedback fixes
- Document overlap ignore paths in storage/settings docs and include a changeset for @runfusion/fusion
When users have an external pi-claude-cli (e.g. a global `npm install -g
pi-claude-cli`, or `npm:pi-claude-cli` in ~/.pi/agent/settings.json packages),
pi's extension discovery loaded the upstream copy and shadowed our fork. The
upstream has a once-and-lock MCP-config bug that throws "Extension runtime not
initialized" during early streamSimple calls and never recovers.
Adds reconcileClaudeCliPaths in @fusion/core, used by both the daemon's
extension assembly and the engine's per-session registerExtensionProviders, to
drop any path with a `pi-claude-cli` segment that isn't our vendored fork and
prepend the vendored path. Engine resolves the fork via require.resolve and
gracefully no-ops when it isn't reachable (e.g. embedded standalone usage).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TUI quit ('q'/Ctrl+C) bypassed signal handlers via process.exit(0), and
neither shutdown path closed the HTTP server, so server.close()'s
stopAllDevServers() listener never ran. In-flight agent bash commands
(spawned detached for their own pgroup) were also never aborted, so
their subprocess trees — including vitest workers — survived as orphans.
Route the TUI quit through SIGINT so the registered shutdown handler
runs, await stopAllDevServers() in both shutdown paths, and abort
in-flight bash on every active agent session at the start of the
runtime drain so killProcessTree reaches every grandchild.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Batch 2 of runMaintenance() runs ~10 recover passes back-to-back, each
calling listTasks({ column: ... }) without slim. On busy boards this
materializes every task's activity log into memory ~10× per cycle,
walking the dashboard heap toward the 8 GB V8 limit until OOM. The
archive pass at line 610 already had this fix; extend it to the in-progress
and in-review recover passes that only read steps / paused / worktree /
mergeDetails / postReviewFixCount — all included in the slim projection.
Triage recovers are left non-slim because hasLatestSpecReviewApproval
scans task.log to find the most recent spec review; the triage column
is small so the memory cost is bounded.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mostly mechanical cleanup left over from the earlier test-consolidation pass:
- Update import paths to ../../ for mocks now that test files moved deeper
- Simplify mock setup (drop usePluginUiSlots inline mock, etc.)
- Move engine ipc + runtimes tests into __tests__/ subdirs
- Move dashboard utils tests into __tests__/ subdir
- Refresh fusion-plugin-hermes-runtime/dist artifacts
build-exe.test.ts: spawn-import fix from a parallel branch (resolved during
worktree merge of the CSS extraction work).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Move all co-located *.test.* files into sibling __tests__/ directories so the
layout is consistent across packages (159 renames + content-rewrite moves).
Updates relative imports, vi.mock specifiers, and __dirname/import.meta.url
path resolutions where tests read fixtures from disk.
- Drop tracked tsc-emit alongside engine .ts sources (auth-storage/logger/
skill-resolver/context-limit-detector/pi.{js,d.ts,*.map}). These were
accidentally committed in a merge and the stale pi.js was masking a real
test-mock vs source mismatch (tests imported "../pi.js" and vite preferred
the stale build over pi.ts).
- Add packages/engine/.gitignore to block future src/*.{js,d.ts,map}.
- Refactor plugin pi-module seams (openclaw/paperclip/hermes) to ESM-import
createFnAgent / promptWithFallback / describeModel from @fusion/engine
instead of require()-ing packages/engine/src/pi.js. Adds @fusion/engine to
the two plugin package.jsons that were missing it; exports describeModel
from the engine public API.
- Fix engine test mocks now that they run against current pi.ts: add
ModelRegistry.create static to mocks in pi.test.ts and pi-create-fn-agent
.test.ts; switch three boundary-result toEqual assertions to toMatchObject
so the new content/isError fields don't trip exact-match comparison.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add ntfy base URL to global settings schema/types with persistence coverage and regression tests
- Extend dashboard settings API/routes and Settings modal UI to edit and save a custom ntfy server
- Update notifier runtime to use configured ntfy base URL when sending notifications
- Document the new setting and include a changeset for @runfusion/fusion
- Regenerate fusion-plugin-hermes-runtime build artifacts and manifest metadata for runtime packaging
- Refactor Hermes runtime source into dedicated pi-module, runtime-adapter, and shared type modules
- Expand Hermes and engine plugin-runner tests to validate cross-runtime compatibility behavior
- Update getting-started, settings reference, and Hermes README docs to reflect the current runtime integration guidance
Agents working on a task that depends on other tasks (e.g. documentation
alignment tasks needing the sibling tasks' specs) were repeatedly
rejected by the worktree boundary when reading .fusion/tasks/FN-NNNN/PROMPT.md,
which also contributed to the malformed-tool-result crash we just fixed.
Add a read-only exception to isWorktreeAllowedPath: the read/glob/grep
tools may access .fusion/tasks/*/PROMPT.md and .fusion/tasks/*/task.json
at the project root. Writes and bash cwd remain restricted.
Update the system-prompt boundary docs (executor.ts) so agents know the
exception exists and stop burning turns re-trying rejected reads.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
wrapToolsWithBoundary returned a bare {ok:false,error} object when an
agent tried to read/write/bash outside the worktree. pi-coding-agent
wraps tool returns into a toolResult message whose content field it
expects to be an array of content blocks; a bare object leaves content
undefined, which later crashes downstream with "Cannot read properties
of undefined (reading 'filter')" — the failure we've been chasing on
FN-2479 and similar.
Return { content:[{type:"text",text:...}], isError:true, ok:false, error:... }
so pi records a valid toolResult block while existing callers that
inspect .ok / .error still work.
Diagnostic evidence: transcript tail for the failing task showed three
consecutive `read` toolResults with content=array(len=0) (normalized
from undefined by our earlier guard) immediately before the assistant
message with stopReason="error".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pi-coding-agent swallows its own exceptions onto session.state.errorMessage
without preserving a stack, so fusion sees only "Cannot read properties
of undefined (reading 'filter')" with no indication which message is
malformed. When promptSessionAndCheck rethrows an error that matches the
generic TypeError shape, dump the last few state.messages (role, content
type, toolName, stopReason) so the offending message can be identified
next run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a branch contained commits already on main (duplicate cherry-picks),
the merger's local squash collapsed to an empty commit. The merger then
recorded that empty commit's SHA on mergeDetails.commitSha. The actual
content landed later on main as a different SHA via PR merge, but the
task kept pointing at the orphaned empty commit.
Symptom: TaskCard showed "N files changed" (falling back to
task.modifiedFiles), but the Changes tab in the modal showed nothing
because the API hit `git diff sha^..sha` on the empty commit and
returned no files.
Two fixes:
1. merger.ts: detect empty squash commits and skip storing commitSha,
logging clearly. recoverInterruptedMergingTasks → findLandedTaskCommit
already exists to backfill the right SHA when the real commit lands;
a missing commitSha is a known fallback path the UI already handles.
2. TaskChangesTab.tsx: when the API returns no files for a done task,
fall back to task.modifiedFiles (paths only, no patches) with a clear
note. Mirrors the existing 3-tier fallback in TaskCard.tsx:1090-1124
so card and modal always agree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pi-coding-agent's _getUserMessageText calls content.filter(...) on user
messages; if a message lands in state.messages with content === undefined
(string or array expected), the library throws
"Cannot read properties of undefined (reading 'filter')", which gets
caught and stored on session.state.errorMessage and rethrown without a
stack. Fusion's existing message-content guard already normalized
assistant/toolResult messages — extend it to user messages as well, and
sweep state.messages once when the guard is installed so content loaded
from a session file is repaired before the first event fires.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The FN-2370 auto-resolved squash (de5dd6f7d) reverted three commits' worth
of refinements to the claude-cli provider because the branch contained
rebased duplicates of commits already on main. The auto-resolver picked
the older side line-by-line and dropped the newer.
Restored:
- /api/models filter logic (was inverted; emptied every model picker)
- Claude Opus 4.7 catalog entry in pi-claude-cli
- Provider card status text and toast messages (no longer claim a restart
is needed — the extension is always-loaded now)
- POST /api/auth/claude-cli returns restartRequired: false
Prevention:
- Regression tests on the /api/models useClaudeCli filter
- scripts/audit-squash-merge.mjs flags duplicate-cherry-pick risk and
touched-file overlap on any squash commit
- AGENTS.md documents the rebase-before-squash rule and requires the
merging agent to run the audit and triage every flagged item itself
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add formatError() helper that extracts both message and full stack from
unknown caught values, and use it at every status:"failed" catch site in
executor, agent-heartbeat, and triage. Stack traces now land in
store.logEntry outcome (persisted to task.log/activityLog) and in stderr
logger output, so failures like "Cannot read properties of undefined
(reading 'filter')" can be diagnosed without re-running.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each of these tests asserts only that a labeled form field exists in
a section. The consolidated "all settings fields are present across
all sections" test (line 497) covers the same space, and the
accompanying payload-roundtrip tests implicitly require the field to
be present before toggling it.
Dropped 7 presence-only tests:
- Recycle worktrees, Show quick chat button, Auto-completion mode
- Include task ID, Auto-resolve conflicts, Add author attribution
- Smart conflict resolution, groupOverlappingFiles type=checkbox
4,317 → 4,206 LOC (-111). 197 tests still passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>