Commit Graph

23 Commits

Author SHA1 Message Date
gsxdsm
338dc173ff feat: pnpm dev --isolated runs against its own database and project
Working on Fusion from inside a machine that already runs one, a plain
`pnpm dev` shares that instance's LIVE database. Everything durable hangs off
$HOME/.fusion — settings, credentials, central DB, the embedded Postgres data
dir — and a process pointed at a data dir whose postmaster is already running
attaches to it instead of starting its own.

--isolated spawns the dev child with HOME pointed at a sandbox, so it gets its
own settings, credentials and Postgres cluster on its own port. It also moves
the child's cwd, which is the half that is easy to miss: `fn dashboard`
derives its project from the working directory and has no project flag, so
isolating HOME alone leaves both instances on `<repo>/.fusion` — including
`.fusion/tasks/<id>/`, which the orphaned-task-dir sweep re-imports, so a
fresh dev database would adopt the real instance's tasks.

The sandbox defaults to ~/.fusion-dev/<checkout-name>/{home,project}: outside
the work tree so it neither shows up in git status nor dies on a clean
checkout, and keyed by checkout so two clones cannot collide. The project dir
is git init-ed on first use because Fusion projects are git work trees.
Changing cwd is safe because PRELOAD/LOADER/ENTRY are already absolute.

Verified in a container beside a running Fusion: the isolated instance
reported zero projects while the real one reported two, on separate Postgres
clusters (ports 42617 and 38311).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 21:14:31 -07:00
gsxdsm
204772bcc3 fix: show the dev tunnel URL in the TUI instead of painting over it
`pnpm dev --tunnel` printed its banner to stdout, and a TTY run then handed
the screen to DashboardTUI, which repainted over it. The public URL — the
entire output of the flag — was unreadable.

The wrapper forwards the URL to the dev child over the IPC channel it already
uses for the listening report, and the dashboard renders a Tunnel row in the
system panel beside URL and Token.

Capture is order-independent. cloudflared can publish before or after the TUI
is constructed, and an IPC message that arrives with no listener attached is
lost, so the URL is stored at run scope on arrival and applied by whichever
half completes second.

Watch-mode restarts reuse the tunnel (a fresh quick tunnel would hand out a
new hostname on every reload), but the new child knows nothing about it, so
the wrapper re-announces on each listening report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 21:20:34 -07:00
gsxdsm
16e63462cc fix: audit fallout — tunnel port, orphaned dev processes, scrollback clear
Auditing for repeats of the `pnpm dev --tunnel` bugs turned up the same
mistakes elsewhere.

Remote tunnels assumed 4040. ProjectEngine's Cloudflare quick tunnel
hardcoded http://localhost:4040, so a dashboard on an explicit --port, a PORT
override, or runDashboard's EADDRINUSE rebind published a PUBLIC tunnel to
whatever else held 4040. The dashboard now records its bound port
(setLocalDashboardPort, from both runDashboard and headless serve) and the
tunnel reads it, keeping 4040 only as the pre-report default.
register-discovery-routes already derived its port from req.socket.localPort
and is untouched.

Stopping the dev wrapper orphaned everything it started. It installed no
signal handlers, so teardown only ran from the child's close handler:
signalling the wrapper left the dev server AND its cloudflared alive —
observed twice, four surviving processes each time, including a public
trycloudflare URL still serving a dev server believed to be down. Ctrl-C hid
it by signalling the whole process group.

SessionTerminal appended scrollback instead of clearing first, though the
server sends it as a separate frame precisely so the client can clear. Latent
today because every reattach builds a fresh xterm; a duplicated-history bug
the moment an in-place reconnect appears.

And BackupManager's centralDbPath is gone: written, never read, and a
leftover of the removed SQLite backup — the same class of stale artifact that
onboarding was using as evidence about a Postgres install.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 21:09:03 -07:00
gsxdsm
8d76af3e5f docs: correct a stale dev-tunnel comment describing the removed fallback
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 20:40:41 -07:00
gsxdsm
f12b9f8463 fix: wait for the dev server rather than tunnelling a guessed port
When the dev child had not reported a bound port within 60s, the wrapper fell
back to the configured port and published a tunnel to it. In the case the
port fix exists for — a container whose own Fusion owns 4040 — that hands out
a dev-looking URL serving a completely different instance, with only a
passing "which may not be it" warning to show for it.

Observed with a dev server stopped on the interactive `Run central db now?
(Y/n)` prompt: it never listens, so it never reports, so the tunnel published
the container's Fusion instead.

A missing tunnel is a visible problem that explains itself; a tunnel to the
wrong app is a silent one. The wrapper now waits, unbounded, printing a
notice once a minute that names the interactive-prompt case. An explicit
--tunnel=PORT still publishes immediately, since it names a target the dev
child knows nothing about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 20:38:48 -07:00
gsxdsm
6f461a4744 fix: take the dev tunnel's token from the dev server, not a guessed file
The banner re-derived the token from ~/.fusion/settings.json. That is simply
the wrong source: on a real run the file contained no daemonToken while the
dashboard's own banner, two lines above, printed a working one — so the
tunnel claimed no token existed next to a token that plainly did.

The dashboard already holds the resolved token at the point where it reports
its bound port, so it now reports both over the same IPC message and the
wrapper prefers that over anything it could derive. The env/settings lookup
survives only for targets that report nothing, such as an explicit
--tunnel=PORT aimed at a server the dev child knows nothing about. The token
crosses the existing parent/child channel only; it is never logged or sent
onward.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 20:16:44 -07:00
gsxdsm
0289d26b13 fix: tunnel the dev server's real port, not whatever holds 4040
The tunnel target was resolved from PORT/4040 before anything bound, but an
occupied port makes the dashboard silently rebind to an ephemeral one
(server.listen(0) on EADDRINUSE). With a normal Fusion already running on
4040, `pnpm dev --tunnel` therefore published THAT instance under a
dev-looking URL while the dev server sat unreachable on a random port.

The dashboard now reports the port it actually bound to the dev supervisor
over IPC, and the wrapper tunnels that:

- IPC is enabled whenever --tunnel is set, not only in watch mode; a plain
  tunnel run previously had no channel at all.
- The tunnel waits for the report (60s cap, then falls back to the configured
  port with a warning), so it also cannot come up against a port nothing is
  serving yet.
- A mismatch is logged instead of silent.
- A reported port is treated as the dashboard whatever its number, so the
  banner keeps printing the bearer token; without that, the ephemeral case
  would classify the dev dashboard as a foreign port and drop it.
- An explicit --tunnel=PORT names a target the dev child knows nothing about,
  so it never waits and is still compared against the configured port.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 18:49:42 -07:00
gsxdsm
ee57f8a3b9 fix: print the dashboard token with the dev tunnel URL
`pnpm dev --tunnel` published a bare URL under a "public, unauthenticated"
header. That label was wrong for the flag's own default target: --tunnel with
no port aims at the dashboard, which is bearer-token gated, so the recipient
of a shared link got a 401 with no token to supply.

resolveDevTunnelAuth() now classifies the target and the banner says what is
actually true of it:

  token         dashboard with auth on — prints the token and a ?token= link,
                resolved from FUSION_DASHBOARD_TOKEN, FUSION_DAEMON_TOKEN,
                then ~/.fusion/settings.json
  token-pending first run, token not minted yet — defers to the dashboard's
                own startup banner
  no-auth       --no-auth is on; the dashboard really is open
  foreign       a non-dashboard port; Fusion has no auth to lend it, the only
                genuinely ungated case

Auth resolves at banner time rather than flag-parse time so a token the dev
child mints on a first authenticated run is already readable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 18:21:50 -07:00
gsxdsm
7423555c46 feat(dev): pnpm dev --tunnel publishes the dev server over a quick tunnel
Operator case: someone works inside a remote Fusion (a container, a shared box),
starts a dev server there, and needs to view it from their own browser. The dev
server binds inside that machine, so without a tunnel the only options are port
publishing or a VPN — both needing cooperation from whoever owns the host.

  pnpm dev --tunnel            # tunnels the dashboard port (PORT, default 4040)
  pnpm dev --tunnel=5173       # tunnels a Vite dev server instead
  pnpm dev --tunnel dashboard  # tunnel the default port AND run the dashboard
  FUSION_DEV_TUNNEL=1 pnpm dev

Cloudflare QUICK tunnels are usable here precisely because a dev server is HTTP:
no account, no domain, no card. The TCP endpoints that SSH would have needed
require a card (ngrok) or a domain plus Zero Trust (Cloudflare) — that asymmetry
is why this exists for HTTP only, and it is recorded in the module header so the
next person does not retry the SSH variant.

Design decisions:
- Tunnel failure is NON-FATAL. A missing cloudflared or a tunnel that never
  publishes a URL logs and is skipped; losing a preview URL must never cost the
  operator their dev loop.
- Watch-mode restarts reuse the existing tunnel. A fresh quick tunnel hands out a
  different hostname each time, which would invalidate an already-shared link.
- `--tunnel` consumes a following token only when it is numeric, so
  `--tunnel dashboard` forwards `dashboard` to the dev command rather than
  tunnelling port NaN. That is the bug this flag shape invites, so it is tested.

Verified end to end in a container: a dev server bound to 127.0.0.1 inside it was
fetched from the public internet through the tunnel (200, correct body). Also
confirmed that tunnelling the DASHBOARD port does not weaken auth — unauthenticated
requests through the tunnel return 401 for /api/tasks, /api/settings and
/api/artifacts, with only /api/health open by design.

Adding two fields to parseDevWrapperArgs' return broke two existing strict toEqual
assertions; those were updated rather than loosened to toMatchObject. 27 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 17:18:56 -07:00
gsxdsm
9e4a0817db feat: restart the development engine on source changes (#3329)
## Summary

Add an opt-in source-development loop that restarts the dashboard and
engine when runtime TypeScript or JSON changes. Use `pnpm dev:watch`;
`pnpm dev:hmr` now combines Vite UI HMR with the same supervised
API/engine restart path.

The watcher filters tests, fixtures, generated declarations, build
output, and task state. It coalesces bursts with a two-second maximum
wait, waits for the child to acknowledge its IPC listener, and rebuilds
runtime dist artifacts before a source-triggered respawn.

## Safety model

- Close scheduler, triage, heartbeat, mission, routine, self-healing,
and merge admission before checking for active work.
- Let already-running agents reach a safe boundary; do not mutate
durable pause settings.
- Enter the existing graceful exit-code-86 shutdown and supervised
respawn path.
- Retry failed liveness reads and declined restart requests instead of
dropping the pending change.
- Keep ordinary `pnpm dev` behavior unchanged; inherited watch state
does not break nested non-dashboard development commands.

A development restart intentionally replaces the dashboard process, so
transient dashboard connections and project dev-server children
reconnect or restart with it. Agent work is the protected boundary.

## Validation

- `pnpm lint`
- `pnpm test:gate` (753 tests passed across engine, core, PostgreSQL
gate, and CI-shape suites)
- Focused CLI watcher/restart/supervision suites: 40 tests passed
- Focused engine drain/manager suites: 52 tests passed
- `pnpm --filter @runfusion/fusion typecheck`
- `pnpm --filter @fusion/engine typecheck`
- `pnpm verify:fast` (13 steps passed, including CLI build and real
health boot smoke)
- Manual unsupported-command probe confirms explicit `--watch` fails
clearly outside the dashboard command

## Post-Deploy Monitoring & Validation

- Watch for `[fusion:dev] source changed`, `source restart deferred`,
`active work drained`, and `restart requested` logs during the first
watched development session.
- Healthy behavior is one exit-86 respawn per edit batch, no interrupted
active agents, refreshed dist artifacts, and a healthy dashboard after
respawn.
- Investigate repeated restart loops, watcher attachment warnings,
declined restart retries, or liveness-read failures.
- Immediate mitigation is to use ordinary `pnpm dev` without `--watch`;
no production runtime behavior or durable setting needs rollback.
- Validation owner: Fusion maintainers during the first source edit
after merge.

---

[![Compound
Engineering](https://img.shields.io/badge/Built_with-Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added `pnpm dev:watch` to automatically restart development runtime
processes when source files change.
* Development restarts now wait for active work to finish, preventing
new work from starting during the transition.
* Enhanced `pnpm dev:hmr` with graceful runtime source restarts while
keeping the dashboard available.
  * Rapid source changes are grouped to avoid unnecessary restarts.

* **Documentation**
* Updated development setup and contribution guides with the new watch
workflow.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-04 08:57:30 -07:00
gsxdsm
99b80ad748 feat(dashboard): add opt-in auto-update and harden restart supervision
Add the `autoUpdateAndRestart` global setting (default off, Settings ->
General next to Release channel). When enabled, the dashboard host installs
available updates on the selected channel by itself and requests the
supervised in-place restart. Supervised hosts only: without a parent to
respawn, installing would leave a running process whose code no longer
matches its own install.

Fix two ways the restart affordance could silently do nothing:

- The supervisor now stamps FUSION_SUPERVISOR_PID and supervision is only
  counted when that pid is the real parent. FUSION_RESTART_SUPERVISED is
  inherited by every process Fusion spawns, so `fn dashboard` launched from
  an agent terminal skipped its own supervisor while still advertising
  restart support -- a restart request then killed it for good.
- Settings and the update banner probe /system/info on mount and treat
  capability as advisory: the button always issues the request and shows the
  server's actual refusal instead of sitting disabled after a failed probe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 22:52:53 -07:00
gsxdsm
a227b19a22 feat: add Command Center System panel with rebuild/restart controls, Plugins tab, and supervised-by-default dashboard
- pnpm dev / new pnpm start default to the dashboard command
- fn dashboard (and bare fn/fusion/npx, incl. packaged binaries) now runs
  supervised by default via an attached foreground child (TUI-safe);
  --no-supervise opts out; FUSION_RESTART_EXIT_CODE=86 = intentional restart
- New /api/system routes: info, restart, rebuild jobs with SSE output,
  engine restart, agents restart-all, plugins reload-all, log tail
- System tab: rebuild & restart (source checkouts only, hidden elsewhere),
  restart server/engine/agents, backup DB, live server logs, copy
  diagnostics, report bug; new Plugins tab reusing PluginManager
- Desktop restart via Electron app.relaunch(); DashboardLogSink now keeps a
  bounded history + listener feed for the log viewer

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:42:09 -07:00
gsxdsm
538bf42e32 fix(dev): rebuild core+engine+dashboard on dev/local startup + stale-dist check
pnpm dev/local dashboard prebuild now rebuilds @fusion/core and @fusion/engine alongside the dashboard UI (was client-only), and startup warns loudly when built dist/ is older than src/. Prevents the FN-6638 class where landed engine fixes silently never run because the process loads stale dist.
2026-06-18 13:41:37 -07:00
Aron Prins
a5ad2c2a1d fix(FN-000): address startup review feedback 2026-05-11 13:36:51 +02:00
Aron Prins
ac64aa5c87 fix(FN-000): improve local startup UX 2026-05-11 09:52:23 +02:00
gsxdsm
a3ce8e1f8e fix(FN-XXX): unify codex auth and chat fallback 2026-05-03 23:08:05 -07:00
gsxdsm
a9a9401006 chore(dev): drop --heapsnapshot-near-heap-limit auto-dump
That flag was added for diagnosing the Ink/perf_hooks leak. Now that the
leak is fixed, leaving it on means a 1GB+ snapshot file gets dumped to
cwd whenever heap pressure hits — noisy and not useful day-to-day.
Inspector flag forwarding is kept for normal --inspect usage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 10:00:15 -07:00
gsxdsm
bff40c07b9 fix(dev): pass --inspect as CLI flag, not via NODE_OPTIONS
NODE_OPTIONS is inherited by every grandchild node process. With --inspect
in there, every vitest worker, agent subprocess, and claude invocation the
dashboard spawned tried to bind 127.0.0.1:9229 and emitted "address already
in use" — those failure strings ended up in agent logs (visible in heap
diagnostics). Pass inspector flags directly on the node command line so
they apply only to the dashboard process.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 08:32:07 -07:00
gsxdsm
84ca1f99dc fix(cli): drain perf_hooks buffer to stop Ink dev-mode heap leak
Ink/react-reconciler in dev mode emits performance.mark/measure() on every
render. Without a PerformanceObserver, Node retains every entry in the User
Timing buffer forever — ~165 renders/sec in the TUI accumulate ~600MB of
PerformanceMeasure objects + diff strings ("Components ⚛", "Changed Props",
"– children", etc.) over 20-30min, which is what was OOM-crashing long dev
sessions. A 30s interval calling clearMeasures()/clearMarks() drains it.

Also teach scripts/dev-with-memory.mjs to forward --inspect /
--inspect-brk, plus auto-add --heapsnapshot-near-heap-limit=3 so any future
heap-limit OOM auto-dumps. The script now invokes node directly with tsx's
preflight + loader (resolved via require.resolve) instead of going through
the .bin/tsx wrapper, so the inspector attaches to the real app process
instead of a parent wrapper that exits before serving the websocket.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 08:10:40 -07:00
Fusion
908b016727 feat(FN-2488): add terminal refresh fallbacks for terminal startup failures
- Add refresh-page actions alongside retry/reinitialize in TerminalModal bootstrap and xterm init error states
- Add terminal error action-group styling so multiple recovery buttons render cleanly
- Extend TerminalModal tests to cover new refresh controls and verify window reload behavior
- Remove unused TaskLogEntry import and drop the stale hooks eslint suppression in dashboard-tui app
2026-04-25 07:34:58 -07:00
Fusion
4e85969d56 perf(executor): recover approved steps on engine restart
When the engine restarts mid-step, an in-progress step may have already
passed plan + code review but not yet been flipped to done by the agent's
next task_update call. Previously, the next executor pass re-entered the
step and replayed both reviews — measured at 5-20 min of pure waste per
restart (observed in FN-2215 Step 1 and FN-2207 Step 6).

recoverApprovedStepsOnResume scans the task log for any in-progress step
whose most recent "code review Step N: APPROVE" entry is newer than its
most recent "Step N → pending" transition, and marks those steps done
before execute() runs. Safely skips steps that were reset after approval
(e.g. by a workflow revision) or only received REVISE verdicts.

Called from both the engine-restart path (resumeOrphaned) and the
unpause path, matching the two places the task log shows as vulnerable
to this race.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 20:12:00 -07:00
gsxdsm
ba5198b813 refactor: replace primary/secondary engine pattern with uniform ProjectEngineManager
Remove the anti-pattern where the cwd project was treated as "primary" with a
special engine, and other projects got "secondary" engines through a separate
code path. Every project now gets an identical ProjectEngine created through
ProjectEngineManager.

Key changes:
- Add ProjectEngineManager class to @fusion/engine for uniform engine lifecycle
- Replace manual engine maps in dashboard.ts and serve.ts with engineManager
- Add engineManager to ServerOptions for per-project engine resolution
- Add getProjectContext() helper in routes.ts (replaces 199 getScopedStore calls)
- Merge and automation routes now resolve engine subsystems per-request
- SSE endpoint uses engine's store when available (same EventEmitter)
- Fix tsx not found in dev-with-memory.mjs startup script
- Add invalidateAllGlobalSettingsCaches for cross-project settings sync

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 13:32:38 -07:00
gsxdsm
f8f002cb5d feat(FN-1531): add memory-aware dev entrypoint
- Add scripts/dev-with-memory.mjs entrypoint script
- Configure memory monitoring and optimization for development mode
- Update package.json scripts to include memory-aware dev option
2026-04-10 02:42:11 -07:00