feat(tui+release): persist vitest memory guard, aggregate root CHANGELOG, lockstep all packages
TUI: vitest memory-guard threshold and on/off toggle now persist to global settings (vitestAutoKillEnabled / vitestKillThresholdPct), so they survive dashboard restarts. Stats panel shows the system-memory used percentage next to used/free. Utilities panel exposes [+/-] to adjust the threshold in 5% steps (50–99%). Release: scripts/release.mjs auto-syncs a root CHANGELOG.md aggregated from every packages/*/CHANGELOG.md, grouped by version with one sub-block per package. Versioning: all private @fusion/* packages joined the changesets fixed group with the public cli + cli-alias and were aligned to 0.2.5, so every release bumps every package and produces per-package CHANGELOG entries that the aggregator picks up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,17 @@
|
|||||||
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
||||||
"changelog": "@changesets/cli/changelog",
|
"changelog": "@changesets/cli/changelog",
|
||||||
"commit": false,
|
"commit": false,
|
||||||
"fixed": [["@runfusion/fusion", "runfusion.ai"]],
|
"fixed": [[
|
||||||
|
"@runfusion/fusion",
|
||||||
|
"runfusion.ai",
|
||||||
|
"@fusion/core",
|
||||||
|
"@fusion/dashboard",
|
||||||
|
"@fusion/desktop",
|
||||||
|
"@fusion/engine",
|
||||||
|
"@fusion/mobile",
|
||||||
|
"@fusion/pi-claude-cli",
|
||||||
|
"@fusion/plugin-sdk"
|
||||||
|
]],
|
||||||
"linked": [],
|
"linked": [],
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"baseBranch": "main",
|
"baseBranch": "main",
|
||||||
|
|||||||
417
CHANGELOG.md
Normal file
417
CHANGELOG.md
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
# Fusion changelog
|
||||||
|
|
||||||
|
User-facing release notes aggregated across all packages. This file is auto-synced from each `packages/*/CHANGELOG.md` by `scripts/release.mjs` — do not edit by hand.
|
||||||
|
|
||||||
|
## 0.2.5
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 69f789f: TUI: layered defenses for the resize / wrong-height-layout bug
|
||||||
|
|
||||||
|
Materially reduces (but doesn't fully eliminate) the symptom of the header rendering off-screen or the layout taking 1-2 too many rows, especially under tmux/ssh.
|
||||||
|
|
||||||
|
- Enter alternate-screen buffer on start; leave on stop. The TUI gets a dedicated fullscreen surface that doesn't share scrollback.
|
||||||
|
- StatusBar Text children no longer wrap (default `wrap="wrap"` was letting long hotkey + URL strings wrap to 2 rows, throwing the row budget off by 1).
|
||||||
|
- Controller subscribes to `process.stdout` "resize" and calls `inkInstance.clear()` to reset log-update's frame tracking.
|
||||||
|
- App-level resize listener + key-based remount on dimension change so React rebuilds the tree from scratch with fresh bounds.
|
||||||
|
- Root Box gets explicit width + overflow="hidden"; MainHeader outer Box too.
|
||||||
|
- Settings + Utilities side-by-side now stretch to equal heights (UtilitiesPanel switched from `flexShrink={0}` to `flexGrow={1}`).
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 69f789f: TUI: layered defenses for the resize / wrong-height-layout bug
|
||||||
|
|
||||||
|
Materially reduces (but doesn't fully eliminate) the symptom of the header rendering off-screen or the layout taking 1-2 too many rows, especially under tmux/ssh.
|
||||||
|
|
||||||
|
- Enter alternate-screen buffer on start; leave on stop. The TUI gets a dedicated fullscreen surface that doesn't share scrollback.
|
||||||
|
- StatusBar Text children no longer wrap (default `wrap="wrap"` was letting long hotkey + URL strings wrap to 2 rows, throwing the row budget off by 1).
|
||||||
|
- Controller subscribes to `process.stdout` "resize" and calls `inkInstance.clear()` to reset log-update's frame tracking.
|
||||||
|
- App-level resize listener + key-based remount on dimension change so React rebuilds the tree from scratch with fresh bounds.
|
||||||
|
- Root Box gets explicit width + overflow="hidden"; MainHeader outer Box too.
|
||||||
|
- Settings + Utilities side-by-side now stretch to equal heights (UtilitiesPanel switched from `flexShrink={0}` to `flexGrow={1}`).
|
||||||
|
|
||||||
|
- Updated dependencies [69f789f]
|
||||||
|
- @runfusion/fusion@0.2.5
|
||||||
|
|
||||||
|
## 0.2.4
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 88b4ecb: TUI fixes: help overlay no longer crashes, header stays rendered
|
||||||
|
|
||||||
|
- Help overlay (`?` / `h`) crashed with "Encountered two children with the same key" because several shortcut entries share the same display key (`[t]` for Git view AND for Toggle engine pause; `[r]` for Refresh stats AND Refresh agent detail). Switch to index-based keys — each row is unique by position, not by character.
|
||||||
|
- Refresh the help text to reflect the unified header (`[m]` Main, `[b/a/g/t/e]` views), the Settings/Files/Agents `←/→` pane swap, the Git push/fetch shortcuts, the Files hidden-files toggle, and the Logs `G` jump-to-end.
|
||||||
|
- Main view (status mode) header sometimes vanished after a tmux pane switch and stayed missing until a terminal resize. Two fixes: (a) drop the `rows < 10` auto-hide in `MainHeader` — tmux pane switches can briefly report stale or zero dimensions, and a transient `return null` was orphaning the header. (b) Wrap `MainHeader` and `StatusBar` in `flexShrink={0}` boxes inside `StatusModeGrid` and `StatusModeSingle` (matching the prior fix in `InteractiveMode`), so Yoga can't squeeze them to 0 rows when content pressures the row budget.
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 88b4ecb: TUI fixes: help overlay no longer crashes, header stays rendered
|
||||||
|
|
||||||
|
- Help overlay (`?` / `h`) crashed with "Encountered two children with the same key" because several shortcut entries share the same display key (`[t]` for Git view AND for Toggle engine pause; `[r]` for Refresh stats AND Refresh agent detail). Switch to index-based keys — each row is unique by position, not by character.
|
||||||
|
- Refresh the help text to reflect the unified header (`[m]` Main, `[b/a/g/t/e]` views), the Settings/Files/Agents `←/→` pane swap, the Git push/fetch shortcuts, the Files hidden-files toggle, and the Logs `G` jump-to-end.
|
||||||
|
- Main view (status mode) header sometimes vanished after a tmux pane switch and stayed missing until a terminal resize. Two fixes: (a) drop the `rows < 10` auto-hide in `MainHeader` — tmux pane switches can briefly report stale or zero dimensions, and a transient `return null` was orphaning the header. (b) Wrap `MainHeader` and `StatusBar` in `flexShrink={0}` boxes inside `StatusModeGrid` and `StatusModeSingle` (matching the prior fix in `InteractiveMode`), so Yoga can't squeeze them to 0 rows when content pressures the row budget.
|
||||||
|
|
||||||
|
- Updated dependencies [88b4ecb]
|
||||||
|
- @runfusion/fusion@0.2.4
|
||||||
|
|
||||||
|
## 0.2.3
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 0f070d8: TUI header redesign and Settings ←/→ pane navigation
|
||||||
|
|
||||||
|
- Replace the dual section + interactive tab strips with a single unified strip: `[m] Main [b] Board [a] Agents [g] Settings [t] Git [e] Explorer`. Status mode highlights the Main pill; interactive views highlight their own. Number-key shortcuts (1–5) for status sections still work but are no longer rendered in the header chrome.
|
||||||
|
- Width tiers now fit comfortably at every terminal size: full labels at cols ≥ 90, glyph-only at 50–89 (every shortcut still visible), FUSION + active pill only below 50. Help/quit shows at cols ≥ 110.
|
||||||
|
- New `m` shortcut switches to status mode (Main); `s` kept as alias.
|
||||||
|
- Settings interactive view: `←` focuses the list pane, `→` focuses the detail pane. `Tab` still cycles either way (consistent with Agents view).
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 0f070d8: TUI header redesign and Settings ←/→ pane navigation
|
||||||
|
|
||||||
|
- Replace the dual section + interactive tab strips with a single unified strip: `[m] Main [b] Board [a] Agents [g] Settings [t] Git [e] Explorer`. Status mode highlights the Main pill; interactive views highlight their own. Number-key shortcuts (1–5) for status sections still work but are no longer rendered in the header chrome.
|
||||||
|
- Width tiers now fit comfortably at every terminal size: full labels at cols ≥ 90, glyph-only at 50–89 (every shortcut still visible), FUSION + active pill only below 50. Help/quit shows at cols ≥ 110.
|
||||||
|
- New `m` shortcut switches to status mode (Main); `s` kept as alias.
|
||||||
|
- Settings interactive view: `←` focuses the list pane, `→` focuses the detail pane. `Tab` still cycles either way (consistent with Agents view).
|
||||||
|
|
||||||
|
- Updated dependencies [0f070d8]
|
||||||
|
- @runfusion/fusion@0.2.3
|
||||||
|
|
||||||
|
## 0.2.2
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 58688fa: Keep the FUSION header from wrapping when the terminal is narrow. The `MiniLogo` and tab pills had Yoga's default `flexShrink: 1`, so the row's collective content overrunning the width was being absorbed by shrinking every child — including FUSION, which then wrapped to two lines. Pin all fixed-content header children to `flexShrink={0}`; the trailing flexGrow filler absorbs slack instead.
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 58688fa: Keep the FUSION header from wrapping when the terminal is narrow. The `MiniLogo` and tab pills had Yoga's default `flexShrink: 1`, so the row's collective content overrunning the width was being absorbed by shrinking every child — including FUSION, which then wrapped to two lines. Pin all fixed-content header children to `flexShrink={0}`; the trailing flexGrow filler absorbs slack instead.
|
||||||
|
- Updated dependencies [58688fa]
|
||||||
|
- @runfusion/fusion@0.2.2
|
||||||
|
|
||||||
|
## 0.2.1
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 07d7bac: Add a blocking dashboard token-recovery dialog that appears only for daemon bearer-token 401 responses, with set-token or clear-token recovery actions that reload the app.
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [07d7bac]
|
||||||
|
- @runfusion/fusion@0.2.1
|
||||||
|
|
||||||
|
## 0.2.0
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Minor Changes
|
||||||
|
|
||||||
|
- a8f5591: Add support for an optional custom ntfy server URL in notification settings, with default fallback to `https://ntfy.sh` when unset.
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [a8f5591]
|
||||||
|
- @runfusion/fusion@0.2.0
|
||||||
|
|
||||||
|
## 0.1.3
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- c105cfa: Automatically install the bundled Fusion skill into supported agent home directories during `fn init` (`~/.claude/skills/fusion`, `~/.codex/skills/fusion`, and `~/.gemini/skills/fusion`) when missing. Existing installs are preserved, and per-target filesystem errors now warn without failing project initialization.
|
||||||
|
- 86521e2: Fix `pnpm install -g @runfusion/fusion` failing with a 404 for `@fusion/pi-claude-cli`. The vendored pi extension is now bundled into the published package's `dist/pi-claude-cli/` and is no longer listed as an external dependency.
|
||||||
|
- 76961d4: Add a severity filter to the interactive `fn dashboard` TUI Logs tab. Users can now press `f` to cycle `all → info → warn → error` for view-only filtering while preserving the full in-memory ring buffer.
|
||||||
|
- f77dd9d: Prevent stale dashboard service workers from trapping old client bundles, and compute automation cron schedules against UTC so monthly runs stay on day 1 across timezones.
|
||||||
|
- f4d2a4b: Fix `fn dashboard` Logs tab row budgeting so log lines stay above the footer hint on short terminals, including wrapped-message cases.
|
||||||
|
- f77dd9d: Fix dashboard SSE cleanup on browser refresh so stale event streams do not exhaust per-origin browser connections.
|
||||||
|
- 31f021a: Fix dashboard TUI log severity rendering so structured `logger.log(...)` entries routed via `stderr` display with info severity/icon instead of being misclassified as errors.
|
||||||
|
- eef56af: Normalize Fusion skill-facing tool naming to the public `fn_*` namespace and clarify the boundary between extension tools and internal engine runtime tools across skill docs.
|
||||||
|
- 832c32c: Refresh the shipped Fusion skill documentation to match the current `fn_*` extension and CLI surfaces, and replace stale kb-era task/storage examples with Fusion-native `FN-*` and `.fusion` conventions.
|
||||||
|
- dce70bf: Persist `fn dashboard` bearer tokens in the existing global settings store (`~/.fusion/settings.json`) on first authenticated run, then reuse them on subsequent starts. Explicit overrides (`--token`, `FUSION_DASHBOARD_TOKEN`, `FUSION_DAEMON_TOKEN`) and `--no-auth` precedence remain intact.
|
||||||
|
- f078a4e: Add a Settings → Pi Extensions action to reinstall Fusion's bundled Pi package (`npm:@runfusion/fusion`) for self-serve recovery when local Pi skill installs are stale or broken.
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [c105cfa]
|
||||||
|
- Updated dependencies [86521e2]
|
||||||
|
- Updated dependencies [76961d4]
|
||||||
|
- Updated dependencies [f77dd9d]
|
||||||
|
- Updated dependencies [f4d2a4b]
|
||||||
|
- Updated dependencies [f77dd9d]
|
||||||
|
- Updated dependencies [31f021a]
|
||||||
|
- Updated dependencies [eef56af]
|
||||||
|
- Updated dependencies [832c32c]
|
||||||
|
- Updated dependencies [dce70bf]
|
||||||
|
- Updated dependencies [f078a4e]
|
||||||
|
- @runfusion/fusion@0.1.3
|
||||||
|
|
||||||
|
## 0.1.2
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 9bf2981: Add a `planning-awaiting-input` ntfy notification event so users can opt in to alerts when planning sessions pause for user input.
|
||||||
|
- Fix the CLI init command import path for the Claude skills runner so tsup can resolve it during build.
|
||||||
|
- 94473c8: Improve dashboard shutdown observability by logging non-fatal diagnostics when `CentralCore.close()` fails during dispose, normal signal shutdown, or dev-mode shutdown cleanup.
|
||||||
|
- Fix dashboard and serve command plugin store initialization to support task store implementations that expose `getFusionDir()` without `getRootDir()`.
|
||||||
|
- c01892d: Route dashboard runtime diagnostics through the shared injected runtime logger so TTY sessions can capture server/package logs in the TUI while preserving readable non-TTY startup banner output.
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [9bf2981]
|
||||||
|
- Updated dependencies
|
||||||
|
- Updated dependencies [94473c8]
|
||||||
|
- Updated dependencies
|
||||||
|
- Updated dependencies [c01892d]
|
||||||
|
- @runfusion/fusion@0.1.2
|
||||||
|
|
||||||
|
## 0.1.1
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 39f7709: Dashboard TUI now surfaces engine log output in the Logs tab. Previously, the engine's `createLogger()` writes (scheduler, executor, triage, merger, PR monitor, heartbeat, etc.) went straight to `console.error` and were rendered beneath the alt-screen TUI — effectively invisible. `DashboardLogSink.captureConsole()` now intercepts `console.log/warn/error` while the TUI is running and routes each line into the ring buffer, parsing a leading `[prefix]` tag so entries carry the subsystem prefix. Originals are restored on TUI shutdown.
|
||||||
|
- 585e480: Add keyboard navigation and inspection features to the Dashboard TUI Logs tab: arrow keys and j/k to navigate entries, Enter to expand selected entry, Esc to close expanded view, and w to toggle wrap mode for long messages.
|
||||||
|
- 86fd24e: `fn dashboard` TTY mode now opens on the System tab first so users immediately see host, port, URL, and auth token access details.
|
||||||
|
- 585e480: Fix dashboard TUI log navigation: add Home/End shortcuts for jumping to first/last log entry, add Space and e keys as alternatives to Enter for expanding logs, improve word wrap to handle long unbroken tokens (URLs, stack traces) by hard-wrapping them at terminal width.
|
||||||
|
- 7d31b21: Fix iOS terminal typing in the dashboard. On touch-primary devices, tapping the terminal opened the on-screen keyboard but keystrokes were silently dropped because the bubble-phase `handleTerminalGestureFocus` handler re-focused the helper textarea and reset its selection during touchstart/pointerdown, disrupting iOS's input-event attribution. The CSS fix in commit c7266b7f already positions the textarea to receive taps natively, so the JS handler is now a no-op on `(hover: none) and (pointer: coarse)` devices and desktop retains click-to-focus.
|
||||||
|
- Fix dashboard TUI log viewport row calculation on very small terminals to prevent log lines from overlapping the footer.
|
||||||
|
- ff5df16: Fix executor model resolution precedence so project `defaultProviderOverride`/`defaultModelIdOverride` is honored before falling back to global `defaultProvider`/`defaultModelId` across execute, hot-swap, and step-session paths.
|
||||||
|
- df2836c: Fix dashboard TUI log behavior so log navigation can reach all entries still present in the ring buffer and streamed merge output is buffered into log lines instead of writing raw fragments into the interactive terminal UI.
|
||||||
|
- bbdd11a: Guard SQLite FTS5 usage so Fusion starts cleanly on Node builds whose bundled `node:sqlite` was compiled without FTS5. On affected systems, `fn dashboard` previously crashed on first run with `Error: no such module: fts5` during schema migration. The Database and ArchiveDatabase now probe for FTS5 at startup and skip the virtual table + triggers when unavailable; `TaskStore.searchTasks` and `ArchiveDatabase.search` fall back to LIKE-based scans. Set `FUSION_DISABLE_FTS5=1` to force the fallback on runtimes where FTS5 is present but undesirable.
|
||||||
|
- 0bb0100: Update dashboard TUI header branding from "fn board" to "fusion" for consistent product naming.
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
Catch-up version bump so `runfusion.ai` stays in sync with `@runfusion/fusion`. The two packages are now grouped under changesets `fixed` in `.changeset/config.json` and will always share a version number from here on.
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Minor Changes
|
||||||
|
|
||||||
|
- 25d44e1: Add interactive TUI to `fn dashboard` with five navigable sections: logs, system, utilities, stats, and settings. Keyboard shortcuts enable quick in-terminal navigation (1-5, arrows, q, Ctrl+C, ? for help). The TUI activates automatically in interactive terminal sessions; non-TTY mode (CI, piped output) retains the existing plain-text banner/log behavior.
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- a2ed6d0: Fixes for stuck merges and agent lifecycle controls.
|
||||||
|
|
||||||
|
- `findLandedTaskCommit` now falls back to scanning all of `HEAD` when the bounded `baseCommitSha..HEAD` range returns no commits (e.g. baseCommitSha was advanced past the landed merge by a fast-forward rebase). Previously the recovery silently returned null and re-queued the merge even though the commit had already landed.
|
||||||
|
- Agent heartbeat triggers and registration are gated by `runtimeConfig.enabled` rather than transient agent state, so paused/idle/error agents stay registered for triggers and re-arm immediately on resume without waiting for a state transition.
|
||||||
|
- `AgentDetailView` exposes a Stop control alongside Pause/Retry for `running` and `error` states so operators can terminate stuck agents without going through the agents list.
|
||||||
|
|
||||||
|
## 0.0.6
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Re-ship three previously reverted fixes and add pre-merge remote rebase.
|
||||||
|
|
||||||
|
- `--no-auth` flag now correctly suppresses bearer-token auth instead of being silently overridden by a stale `FUSION_DAEMON_TOKEN` in the project's `.env`.
|
||||||
|
- Workflow-review revisions reopen only the last step rather than resetting every previously-completed step. The agent applies the feedback as an in-place fix and earlier approved work stays done. New `reopenLastStepForRevision` helper is used by `handleWorkflowRevisionRequest`, `handleWorkflowStepFailure`, and `sendTaskBackForFix`. `determineRevisionResetStart` is marked `@deprecated` and kept exported for tests.
|
||||||
|
- Heartbeat scheduling is now driven by `agent.state` (`active`/`running` = timer armed; everything else = timer cleared), not `runtimeConfig.enabled`. Resuming a paused agent through the dashboard now re-arms the timer immediately.
|
||||||
|
- New setting `worktreeRebaseBeforeMerge` (default `true`) and companion `worktreeRebaseRemote` (default: git's configured default). The merger fetches the remote and rebases the task branch onto the latest default-branch tip before merging; conflicts flow into the existing smart/AI resolve cascade. Dashboard Settings → Worktrees exposes a checkbox and a remote dropdown populated from `/api/git/remotes/detailed`.
|
||||||
|
- Last/Next heartbeat labels on the agent list card now share font-size and inline-flex alignment so they line up cleanly.
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
- @runfusion/fusion@0.0.6
|
||||||
|
|
||||||
|
## 0.0.5
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 41553a5: Harden agent lifecycle around closed tasks and heartbeat defaults.
|
||||||
|
|
||||||
|
- `HeartbeatMonitor.executeHeartbeat()` now exits before session creation when the resolved task is done/archived (reason `task_closed`) and clears the stale `agent.taskId` linkage so the guard isn't re-tripped on every tick.
|
||||||
|
- `HeartbeatTriggerScheduler.watchAssignments()` skips callback dispatch when the assigned task is already closed (when a `taskStore` is wired in).
|
||||||
|
- `POST /api/agents/:id/runs` performs the same preflight check and returns 409 with a structured error naming the task id + column, keeping the existing active-run 409 precedence.
|
||||||
|
- `AgentStore.createAgent()` now persists `runtimeConfig.heartbeatIntervalMs` (default 1h) on non-ephemeral agents so the dashboard's freshness signal matches the scheduler's effective cadence instead of depending on whether the user ever opened the heartbeat dropdown. Exports a new `DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS` constant.
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [41553a5]
|
||||||
|
- @runfusion/fusion@0.0.5
|
||||||
|
|
||||||
|
## 0.0.4
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 0da498a: Fix dashboard onboarding auth token controls, keep the AI planning modal footer visible on desktop, and add better terminal PTY spawn diagnostics.
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Bump the alias package to stay in sync with the current Fusion release.
|
||||||
|
|
||||||
|
## 0.0.3
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- 1fc72d1: Improve the dashboard agents list views with shared empty-state actions, token-based state styling, and clearer board/tree/org-chart presentation.
|
||||||
|
- 46b8032: Make `fn agent import` import package skills alongside agents when importing from directory or archive sources. Skills are written to `{project}/skills/imported/{company-slug}/{skill-slug}/SKILL.md` with proper frontmatter formatting. Existing skill files are skipped rather than overwritten. Single AGENTS.md file imports do not include package skills.
|
||||||
|
- c1bc5b9: Fix CLI merge regressions in test/build verification: restore gh-cli test alias resolution, ensure daemon ignores invalid env tokens, and restore required changeset config.
|
||||||
|
- 06704cf: Fix the setup wizard directory browser and make terminal session startup more resilient.
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [0da498a]
|
||||||
|
- @runfusion/fusion@0.0.4
|
||||||
|
|
||||||
|
## 0.0.2
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Add `fusion` bin alias so `npx @runfusion/fusion` resolves to the CLI
|
||||||
|
(the `fn` command is still available and unchanged).
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [1fc72d1]
|
||||||
|
- Updated dependencies [46b8032]
|
||||||
|
- Updated dependencies [c1bc5b9]
|
||||||
|
- Updated dependencies [06704cf]
|
||||||
|
- @runfusion/fusion@0.0.3
|
||||||
|
|
||||||
|
## 0.0.1
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Initial release
|
||||||
|
|
||||||
|
First public release under the `@runfusion` scope. Package was previously
|
||||||
|
developed under the `@gsxdsm/fusion` name; it was never published to npm,
|
||||||
|
so version history resets with `0.0.1`. Pre-release notes preserved below
|
||||||
|
for reference.
|
||||||
|
|
||||||
|
## 0.4.0 (pre-release, unpublished)
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Minor Changes
|
||||||
|
|
||||||
|
- 2d13b82: Add pi extension. Installing `@runfusion/fusion` via `pi install` now provides native tools (`fn_task_create`, `fn_task_list`, `fn_task_show`, `fn_task_attach`, `fn_task_pause`, `fn_task_unpause`) and a `/fn` command to start the dashboard and AI engine from within a pi session.
|
||||||
|
- 494de14: Changed `autoMerge` to default to `true` for new boards.
|
||||||
|
- 50821fc: Add global pause button to stop all automated agents and scheduling
|
||||||
|
- cac10af: Split engine control into Pause (soft) and Stop (hard). The dashboard Header now shows two buttons: "Pause AI engine" stops new work from being dispatched while letting in-flight agents finish gracefully, and "Stop AI engine" (previously the only Pause button) immediately kills all active agent sessions. A new `enginePaused` setting field controls the soft-pause state alongside the existing `globalPause` hard-stop.
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- d19b51f: Auto-assign random port when dashboard port is already in use instead of crashing with EADDRINUSE.
|
||||||
|
- ceb379d: Engine pause now terminates active agent sessions (matching global pause behavior) instead of letting them finish gracefully. Tasks are moved back to todo/cleared for clean resume on unpause.
|
||||||
|
- acb246a: Fix active agent glow disappearing when scheduling is soft-paused
|
||||||
|
- 43aada5: Fix scheduler to not count in-review worktrees against maxWorktrees limit. In-review tasks are idle (waiting to merge) and no longer block new tasks from starting.
|
||||||
|
- 9033a79: Fix InlineCreateCard cancelling when clicking dependency dropdown items with empty description.
|
||||||
|
- 96f1070: Fix double horizontal scrollbar on mobile board view by switching the board from a 5-column grid to a flex layout on narrow viewports (≤768px) with snap-scrolling.
|
||||||
|
- 3dc741c: Fix auto-pause on rate limit when pi-coding-agent exhausts retries. After `session.prompt()` resolves with exhausted retries, all four agent types (executor, triage, merger, reviewer) now detect the error on `session.state.error` and trigger `UsageLimitPauser` to activate global pause. Previously, rate-limit errors that pi-coding-agent handled internally were silently swallowed, causing tasks to be promoted to wrong columns with incomplete work.
|
||||||
|
- 2854553: Fix triage allowing tasks to reach executor before spec review approval
|
||||||
|
- 72a8953: Fix specifying agents not respecting maxConcurrent concurrency limit
|
||||||
|
- a2a12f9: Persist worktree pool across engine restarts. When `recycleWorktrees` is enabled, idle worktrees are rehydrated from disk on startup instead of being forgotten. When disabled, orphaned worktrees are cleaned up automatically.
|
||||||
|
- 65b9585: Add priority-based agent scheduling: merge agents are served before execution agents, which are served before specification agents, when competing for concurrency slots.
|
||||||
|
- 98ed082: Restructure README to lead with pi extension usage; move standalone CLI docs to STANDALONE.md.
|
||||||
|
- 2d13b82: Agents now declare dependencies when creating multiple related tasks during execution
|
||||||
|
- 0e0643a: Skip merger agent when squash merge stages nothing (branch already merged via dependency)
|
||||||
|
- d2e2e50: Make "Pause AI engine" a soft pause: only prevents new agents from starting while allowing currently running agents to finish their work naturally. "Stop AI engine" (global pause) still immediately terminates all active agents.
|
||||||
|
- 90764b9: Auto-pause engine when API usage limits are detected (rate limits, overloaded, quota exceeded). Prevents wasteful retries across concurrent agents.
|
||||||
|
|
||||||
|
## 0.3.1
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- ae90be0: Bundle workspace packages into CLI for npm publish. The published package previously declared dependencies on private `@kb/core`, `@kb/dashboard`, and `@kb/engine` workspace packages, causing `npm install` to fail. Switched the CLI build from `tsc` to `tsup` (esbuild) to inline all `@kb/*` workspace code into a single bundled `dist/bin.js`, while keeping third-party packages (`express`, `multer`, `@mariozechner/pi-ai`) as external dependencies. Dashboard client assets are now copied into `dist/client/` so the published tarball is fully self-contained.
|
||||||
|
- 28bbcb9: Exclude Bun-compiled platform binaries from npm publish tarball, reducing package size significantly.
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
### @runfusion/fusion
|
||||||
|
|
||||||
|
#### Minor Changes
|
||||||
|
|
||||||
|
- fc7582d: Expand agent.log logging to all agent types, additionally capturing thinking, and agent roles
|
||||||
|
- cc999ef: RETHINK verdicts trigger git reset and conversation rewind, re-prompting the agent with feedback
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- f3c7f7d: CLI `task create` now supports a `--depends <id>` flag (repeatable) to declare task dependencies at creation time.
|
||||||
|
- fc7582d: Code review REVISE verdicts are now enforced such that agents can no longer advance steps without APPROVE
|
||||||
|
- cc999ef: Plan RETHINK triggers conversation rewind with REVISE enforcement on code reviews
|
||||||
|
- cc999ef: Dependent tasks can start from in-review dependency branches instead of waiting for merge
|
||||||
|
|
||||||
|
## 0.0.8
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [39f7709]
|
||||||
|
- Updated dependencies [585e480]
|
||||||
|
- Updated dependencies [86fd24e]
|
||||||
|
- Updated dependencies [585e480]
|
||||||
|
- Updated dependencies [7d31b21]
|
||||||
|
- Updated dependencies
|
||||||
|
- Updated dependencies [ff5df16]
|
||||||
|
- Updated dependencies [df2836c]
|
||||||
|
- Updated dependencies [bbdd11a]
|
||||||
|
- Updated dependencies [0bb0100]
|
||||||
|
- @runfusion/fusion@0.1.1
|
||||||
|
|
||||||
|
## 0.0.7
|
||||||
|
|
||||||
|
### runfusion.ai
|
||||||
|
|
||||||
|
#### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [25d44e1]
|
||||||
|
- Updated dependencies [a2ed6d0]
|
||||||
|
- @runfusion/fusion@0.1.0
|
||||||
@@ -378,6 +378,11 @@ function StatsPanel({ state, isFocused }: { state: DashboardState; isFocused: bo
|
|||||||
<Text dimColor>used</Text>
|
<Text dimColor>used</Text>
|
||||||
<Text>{formatBytes(sys.systemFreeMem)}</Text>
|
<Text>{formatBytes(sys.systemFreeMem)}</Text>
|
||||||
<Text dimColor>free</Text>
|
<Text dimColor>free</Text>
|
||||||
|
{sys.systemTotalMem > 0 && (
|
||||||
|
<Text color={sysMemColor(sys.systemTotalMem - sys.systemFreeMem, sys.systemTotalMem)}>
|
||||||
|
{((sys.systemTotalMem - sys.systemFreeMem) / sys.systemTotalMem * 100).toFixed(1)}%
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</StatRow>
|
</StatRow>
|
||||||
<StatRow label="Cores">
|
<StatRow label="Cores">
|
||||||
<Text>{sys.cpuCount}</Text>
|
<Text>{sys.cpuCount}</Text>
|
||||||
@@ -591,12 +596,14 @@ function ExpandedLog({ entry, index, total }: { entry: LogEntry; index: number;
|
|||||||
|
|
||||||
function UtilitiesPanel({ state, isFocused }: { state: DashboardState; isFocused: boolean }) {
|
function UtilitiesPanel({ state, isFocused }: { state: DashboardState; isFocused: boolean }) {
|
||||||
const autoKill = state.autoKillVitestOnPressure;
|
const autoKill = state.autoKillVitestOnPressure;
|
||||||
|
const thresholdPct = Math.round(state.vitestKillThreshold * 100);
|
||||||
const actions: Array<{ key: string; label: string }> = [
|
const actions: Array<{ key: string; label: string }> = [
|
||||||
{ key: "r", label: "Refresh Stats" },
|
{ key: "r", label: "Refresh Stats" },
|
||||||
{ key: "c", label: "Clear Logs" },
|
{ key: "c", label: "Clear Logs" },
|
||||||
{ key: "t", label: "Toggle Engine Pause" },
|
{ key: "t", label: "Toggle Engine Pause" },
|
||||||
{ key: "k", label: "Kill Vitest Processes" },
|
{ key: "k", label: "Kill Vitest Processes" },
|
||||||
{ key: "v", label: `Auto-Kill Vitest >90% Mem: ${autoKill ? "ON" : "OFF"}` },
|
{ key: "v", label: `Auto-Kill Vitest >${thresholdPct}% Mem: ${autoKill ? "ON" : "OFF"}` },
|
||||||
|
{ key: "+/-", label: `Adjust Threshold (${thresholdPct}%)` },
|
||||||
{ key: "?", label: "Help" },
|
{ key: "?", label: "Help" },
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
@@ -632,7 +639,8 @@ function HelpOverlay() {
|
|||||||
["[r]", "Refresh stats (Utilities)"],
|
["[r]", "Refresh stats (Utilities)"],
|
||||||
["[c]", "Clear logs (Utilities)"],
|
["[c]", "Clear logs (Utilities)"],
|
||||||
["[k]", "Kill all vitest processes (Utilities)"],
|
["[k]", "Kill all vitest processes (Utilities)"],
|
||||||
["[v]", "Toggle auto-kill vitest >90% mem (Utilities)"],
|
["[v]", "Toggle auto-kill vitest on memory pressure (Utilities)"],
|
||||||
|
["[+/-]", "Adjust vitest kill memory threshold (Utilities)"],
|
||||||
["[↑/↓/k/j]", "Navigate list / log entries"],
|
["[↑/↓/k/j]", "Navigate list / log entries"],
|
||||||
["[Home / G]", "First / last log entry (Logs)"],
|
["[Home / G]", "First / last log entry (Logs)"],
|
||||||
["[Enter/Space]", "Expand log entry (Logs)"],
|
["[Enter/Space]", "Expand log entry (Logs)"],
|
||||||
@@ -769,7 +777,7 @@ function StatusBar({ state, controller: _controller }: { state: DashboardState;
|
|||||||
if (activeSection === "logs") {
|
if (activeSection === "logs") {
|
||||||
hotkeys.push("↑↓ navigate", "w wrap", "f filter", "Enter expand");
|
hotkeys.push("↑↓ navigate", "w wrap", "f filter", "Enter expand");
|
||||||
} else if (activeSection === "utilities") {
|
} else if (activeSection === "utilities") {
|
||||||
hotkeys.push("r refresh", "c clear logs", "t toggle pause", "k kill vitest", "v auto-kill");
|
hotkeys.push("r refresh", "c clear logs", "t toggle pause", "k kill vitest", "v auto-kill", "+/- threshold");
|
||||||
} else {
|
} else {
|
||||||
hotkeys.push("Tab cycle panel", "1-5 jump");
|
hotkeys.push("Tab cycle panel", "1-5 jump");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ export class DashboardTUI {
|
|||||||
// When true, sampleSystemStats() kills any running vitest processes if
|
// When true, sampleSystemStats() kills any running vitest processes if
|
||||||
// system memory usage crosses 90%. Toggled by [v] in the Utilities panel.
|
// system memory usage crosses 90%. Toggled by [v] in the Utilities panel.
|
||||||
autoKillVitestOnPressure = true;
|
autoKillVitestOnPressure = true;
|
||||||
|
// System-memory ratio (0..1) at which auto-kill triggers. Adjustable from
|
||||||
|
// the Utilities panel via [+]/[-] in 5% steps. Clamped to [0.5, 0.99].
|
||||||
|
vitestKillThreshold = 0.9;
|
||||||
// Throttle so we don't spam kills while the sampler keeps firing during
|
// Throttle so we don't spam kills while the sampler keeps firing during
|
||||||
// sustained pressure (sampler runs every 2s).
|
// sustained pressure (sampler runs every 2s).
|
||||||
private lastAutoKillAt = 0;
|
private lastAutoKillAt = 0;
|
||||||
@@ -117,6 +120,7 @@ export class DashboardTUI {
|
|||||||
interactiveData: this.interactiveData,
|
interactiveData: this.interactiveData,
|
||||||
interactiveView: this.interactiveView,
|
interactiveView: this.interactiveView,
|
||||||
autoKillVitestOnPressure: this.autoKillVitestOnPressure,
|
autoKillVitestOnPressure: this.autoKillVitestOnPressure,
|
||||||
|
vitestKillThreshold: this.vitestKillThreshold,
|
||||||
};
|
};
|
||||||
return this.cachedSnapshot;
|
return this.cachedSnapshot;
|
||||||
}
|
}
|
||||||
@@ -210,12 +214,12 @@ export class DashboardTUI {
|
|||||||
const usedRatio = (total - free) / total;
|
const usedRatio = (total - free) / total;
|
||||||
// 30s minimum gap between auto-kills — vitest restart and OS reclaim
|
// 30s minimum gap between auto-kills — vitest restart and OS reclaim
|
||||||
// both take a few seconds; firing every 2s would flap.
|
// both take a few seconds; firing every 2s would flap.
|
||||||
if (usedRatio > 0.9 && now - this.lastAutoKillAt > 30_000) {
|
if (usedRatio > this.vitestKillThreshold && now - this.lastAutoKillAt > 30_000) {
|
||||||
this.lastAutoKillAt = now;
|
this.lastAutoKillAt = now;
|
||||||
const result = this.killVitestProcesses();
|
const result = this.killVitestProcesses();
|
||||||
if (result.killed > 0) {
|
if (result.killed > 0) {
|
||||||
this.warn(
|
this.warn(
|
||||||
`Auto-killed ${result.killed} vitest process${result.killed === 1 ? "" : "es"} (system memory at ${Math.round(usedRatio * 100)}%)`,
|
`Auto-killed ${result.killed} vitest process${result.killed === 1 ? "" : "es"} (system memory at ${Math.round(usedRatio * 100)}%, threshold ${Math.round(this.vitestKillThreshold * 100)}%)`,
|
||||||
"memory-guard",
|
"memory-guard",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -257,15 +261,49 @@ export class DashboardTUI {
|
|||||||
return { killed, pids };
|
return { killed, pids };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adjustVitestKillThreshold(deltaPct: number): number {
|
||||||
|
const next = this.vitestKillThreshold + deltaPct / 100;
|
||||||
|
this.vitestKillThreshold = Math.max(0.5, Math.min(0.99, Math.round(next * 100) / 100));
|
||||||
|
this.notify();
|
||||||
|
void this.persistVitestKillSettings({ thresholdPct: Math.round(this.vitestKillThreshold * 100) });
|
||||||
|
return this.vitestKillThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
toggleAutoKillVitest(): boolean {
|
toggleAutoKillVitest(): boolean {
|
||||||
this.autoKillVitestOnPressure = !this.autoKillVitestOnPressure;
|
this.autoKillVitestOnPressure = !this.autoKillVitestOnPressure;
|
||||||
if (!this.autoKillVitestOnPressure) {
|
if (!this.autoKillVitestOnPressure) {
|
||||||
this.lastAutoKillAt = 0;
|
this.lastAutoKillAt = 0;
|
||||||
}
|
}
|
||||||
this.notify();
|
this.notify();
|
||||||
|
void this.persistVitestKillSettings({ enabled: this.autoKillVitestOnPressure });
|
||||||
return this.autoKillVitestOnPressure;
|
return this.autoKillVitestOnPressure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Apply persisted values from global settings on startup. Does not
|
||||||
|
* trigger a write-back. */
|
||||||
|
hydrateVitestKillSettings(values: { enabled?: boolean; thresholdPct?: number }): void {
|
||||||
|
if (typeof values.enabled === "boolean") {
|
||||||
|
this.autoKillVitestOnPressure = values.enabled;
|
||||||
|
}
|
||||||
|
if (typeof values.thresholdPct === "number" && Number.isFinite(values.thresholdPct)) {
|
||||||
|
const ratio = values.thresholdPct / 100;
|
||||||
|
this.vitestKillThreshold = Math.max(0.5, Math.min(0.99, ratio));
|
||||||
|
}
|
||||||
|
this.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async persistVitestKillSettings(
|
||||||
|
partial: { enabled?: boolean; thresholdPct?: number },
|
||||||
|
): Promise<void> {
|
||||||
|
if (!this.callbacks?.onPersistVitestKillSettings) return;
|
||||||
|
try {
|
||||||
|
await this.callbacks.onPersistVitestKillSettings(partial);
|
||||||
|
} catch {
|
||||||
|
// Best-effort persistence — the in-memory toggle remains in effect
|
||||||
|
// even if disk write fails. The next adjust will retry.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setSettings(settings: SettingsValues): void {
|
setSettings(settings: SettingsValues): void {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.notify();
|
this.notify();
|
||||||
@@ -419,11 +457,23 @@ export class DashboardTUI {
|
|||||||
case "v": {
|
case "v": {
|
||||||
const enabled = this.toggleAutoKillVitest();
|
const enabled = this.toggleAutoKillVitest();
|
||||||
this.log(
|
this.log(
|
||||||
`Auto-kill vitest on memory pressure (>90%): ${enabled ? "ON" : "OFF"}`,
|
`Auto-kill vitest on memory pressure (>${Math.round(this.vitestKillThreshold * 100)}%): ${enabled ? "ON" : "OFF"}`,
|
||||||
"memory-guard",
|
"memory-guard",
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "+":
|
||||||
|
case "=": {
|
||||||
|
const v = this.adjustVitestKillThreshold(+5);
|
||||||
|
this.log(`Vitest kill threshold: ${Math.round(v * 100)}%`, "memory-guard");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "-":
|
||||||
|
case "_": {
|
||||||
|
const v = this.adjustVitestKillThreshold(-5);
|
||||||
|
this.log(`Vitest kill threshold: ${Math.round(v * 100)}%`, "memory-guard");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,12 @@ export interface TUICallbacks {
|
|||||||
onRefreshStats: () => Promise<void>;
|
onRefreshStats: () => Promise<void>;
|
||||||
onClearLogs: () => void;
|
onClearLogs: () => void;
|
||||||
onTogglePause: (paused: boolean) => Promise<SettingsValues>;
|
onTogglePause: (paused: boolean) => Promise<SettingsValues>;
|
||||||
|
/** Persist vitest memory-guard settings to global settings so they
|
||||||
|
* survive across dashboard restarts. Optional — when undefined, the
|
||||||
|
* controller treats them as session-local. */
|
||||||
|
onPersistVitestKillSettings?: (
|
||||||
|
partial: { enabled?: boolean; thresholdPct?: number },
|
||||||
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slim project shape used by interactive mode
|
// Slim project shape used by interactive mode
|
||||||
@@ -280,6 +286,7 @@ export interface DashboardState {
|
|||||||
interactiveData: InteractiveData | null;
|
interactiveData: InteractiveData | null;
|
||||||
interactiveView: InteractiveView;
|
interactiveView: InteractiveView;
|
||||||
autoKillVitestOnPressure: boolean;
|
autoKillVitestOnPressure: boolean;
|
||||||
|
vitestKillThreshold: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SECTION_ORDER: SectionId[] = ["system", "logs", "utilities", "stats", "settings"];
|
export const SECTION_ORDER: SectionId[] = ["system", "logs", "utilities", "stats", "settings"];
|
||||||
@@ -304,5 +311,6 @@ export function createInitialState(): DashboardState {
|
|||||||
interactiveData: null,
|
interactiveData: null,
|
||||||
interactiveView: "board",
|
interactiveView: "board",
|
||||||
autoKillVitestOnPressure: true,
|
autoKillVitestOnPressure: true,
|
||||||
|
vitestKillThreshold: 0.9,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -732,6 +732,18 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
|||||||
globalPause: false,
|
globalPause: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
onPersistVitestKillSettings: async (partial) => {
|
||||||
|
if (!store) return;
|
||||||
|
const patch: Record<string, unknown> = {};
|
||||||
|
if (typeof partial.enabled === "boolean") {
|
||||||
|
patch.vitestAutoKillEnabled = partial.enabled;
|
||||||
|
}
|
||||||
|
if (typeof partial.thresholdPct === "number") {
|
||||||
|
patch.vitestKillThresholdPct = partial.thresholdPct;
|
||||||
|
}
|
||||||
|
if (Object.keys(patch).length === 0) return;
|
||||||
|
await store.getGlobalSettingsStore().updateSettings(patch);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
// Start the TUI
|
// Start the TUI
|
||||||
await tui.start();
|
await tui.start();
|
||||||
@@ -1788,6 +1800,22 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
|||||||
globalPause: settings.globalPause ?? false,
|
globalPause: settings.globalPause ?? false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Hydrate the TUI memory guard from persisted global settings so the
|
||||||
|
// user's previous toggle/threshold survives across dashboard restarts.
|
||||||
|
try {
|
||||||
|
const globalSettings = await store.getGlobalSettingsStore().getSettings();
|
||||||
|
tui.hydrateVitestKillSettings({
|
||||||
|
enabled: typeof globalSettings.vitestAutoKillEnabled === "boolean"
|
||||||
|
? globalSettings.vitestAutoKillEnabled
|
||||||
|
: undefined,
|
||||||
|
thresholdPct: typeof globalSettings.vitestKillThresholdPct === "number"
|
||||||
|
? globalSettings.vitestKillThresholdPct
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// Fall back to controller defaults if global settings can't be read.
|
||||||
|
}
|
||||||
|
|
||||||
// Populate initial stats
|
// Populate initial stats
|
||||||
const tasks = await store.listTasks({ slim: true, includeArchived: false });
|
const tasks = await store.listTasks({ slim: true, includeArchived: false });
|
||||||
const counts = new Map<string, number>();
|
const counts = new Map<string, number>();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fusion/core",
|
"name": "@fusion/core",
|
||||||
"version": "0.1.0",
|
"version": "0.2.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Fusion core: task store, scheduler, settings, and shared domain types backing the Fusion AI coding agent.",
|
"description": "Fusion core: task store, scheduler, settings, and shared domain types backing the Fusion AI coding agent.",
|
||||||
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ export const DEFAULT_GLOBAL_SETTINGS = {
|
|||||||
// Dashboard session state (persisted to global settings for PWA/offline restore)
|
// Dashboard session state (persisted to global settings for PWA/offline restore)
|
||||||
dashboardCurrentNodeId: undefined,
|
dashboardCurrentNodeId: undefined,
|
||||||
dashboardCurrentProjectIdByNode: undefined,
|
dashboardCurrentProjectIdByNode: undefined,
|
||||||
|
// Dashboard TUI memory guard
|
||||||
|
vitestAutoKillEnabled: true,
|
||||||
|
vitestKillThresholdPct: 90,
|
||||||
} satisfies CompleteSettings<GlobalSettings>;
|
} satisfies CompleteSettings<GlobalSettings>;
|
||||||
|
|
||||||
/** Default values for project-level settings. */
|
/** Default values for project-level settings. */
|
||||||
|
|||||||
@@ -1123,6 +1123,15 @@ export interface GlobalSettings {
|
|||||||
* Clear individual entries by setting them to `undefined` (omitting from update).
|
* Clear individual entries by setting them to `undefined` (omitting from update).
|
||||||
* Clearing all entries returns the dashboard to overview mode. */
|
* Clearing all entries returns the dashboard to overview mode. */
|
||||||
dashboardCurrentProjectIdByNode?: Record<string, string>;
|
dashboardCurrentProjectIdByNode?: Record<string, string>;
|
||||||
|
/** When true, the dashboard TUI's memory guard will SIGKILL any running
|
||||||
|
* vitest processes once system memory usage crosses
|
||||||
|
* {@link vitestKillThresholdPct}. The kill is throttled to once per 30
|
||||||
|
* seconds. Default: true. */
|
||||||
|
vitestAutoKillEnabled?: boolean;
|
||||||
|
/** System-memory usage percent (0–100) at which the TUI memory guard
|
||||||
|
* triggers a vitest auto-kill. Clamped to [50, 99] in the UI.
|
||||||
|
* Default: 90. */
|
||||||
|
vitestKillThresholdPct?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fusion/dashboard",
|
"name": "@fusion/dashboard",
|
||||||
"version": "0.1.0",
|
"version": "0.2.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Fusion dashboard: React UI and HTTP API server for monitoring and controlling the Fusion AI coding agent.",
|
"description": "Fusion dashboard: React UI and HTTP API server for monitoring and controlling the Fusion AI coding agent.",
|
||||||
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fusion/desktop",
|
"name": "@fusion/desktop",
|
||||||
"version": "0.1.0",
|
"version": "0.2.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Fusion desktop: Electron wrapper around the Fusion dashboard for macOS, Windows, and Linux.",
|
"description": "Fusion desktop: Electron wrapper around the Fusion dashboard for macOS, Windows, and Linux.",
|
||||||
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fusion/engine",
|
"name": "@fusion/engine",
|
||||||
"version": "0.1.0",
|
"version": "0.2.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Fusion engine: executor, merger, scheduler, and automation runtime for the Fusion AI coding agent.",
|
"description": "Fusion engine: executor, merger, scheduler, and automation runtime for the Fusion AI coding agent.",
|
||||||
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fusion/mobile",
|
"name": "@fusion/mobile",
|
||||||
"version": "0.1.0",
|
"version": "0.2.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Fusion mobile: Capacitor wrapper around the Fusion dashboard for iOS and Android.",
|
"description": "Fusion mobile: Capacitor wrapper around the Fusion dashboard for iOS and Android.",
|
||||||
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fusion/pi-claude-cli",
|
"name": "@fusion/pi-claude-cli",
|
||||||
"version": "0.3.1",
|
"version": "0.2.5",
|
||||||
"description": "Fusion vendored fork: pi coding-agent extension that routes LLM calls through the Claude Code CLI. Forked from rchern/pi-claude-cli (MIT). See UPSTREAM.md.",
|
"description": "Fusion vendored fork: pi coding-agent extension that routes LLM calls through the Claude Code CLI. Forked from rchern/pi-claude-cli (MIT). See UPSTREAM.md.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fusion/plugin-sdk",
|
"name": "@fusion/plugin-sdk",
|
||||||
"version": "0.1.0",
|
"version": "0.2.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Fusion plugin SDK: types and helpers for authoring third-party plugins that extend the Fusion dashboard and engine.",
|
"description": "Fusion plugin SDK: types and helpers for authoring third-party plugins that extend the Fusion dashboard and engine.",
|
||||||
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
// pnpm release --dry-run # run through steps without publishing/pushing
|
// pnpm release --dry-run # run through steps without publishing/pushing
|
||||||
|
|
||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import { readFileSync, readdirSync } from "node:fs";
|
import { readFileSync, readdirSync, writeFileSync, statSync, existsSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
import { createInterface } from "node:readline/promises";
|
import { createInterface } from "node:readline/promises";
|
||||||
import { stdin, stdout } from "node:process";
|
import { stdin, stdout } from "node:process";
|
||||||
|
|
||||||
@@ -43,6 +44,118 @@ function run(cmd, { capture = false, allowFail = false } = {}) {
|
|||||||
return { status: r.status, stdout: (r.stdout || "").trim() };
|
return { status: r.status, stdout: (r.stdout || "").trim() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewrite the repo-root CHANGELOG.md by aggregating every
|
||||||
|
* `packages/*\/CHANGELOG.md` into a single per-version view.
|
||||||
|
*
|
||||||
|
* For each version that appears in any package, we emit a top-level
|
||||||
|
* `## <version>` block, then a `### <pkgName>` sub-block per package that
|
||||||
|
* had an entry for that version, with the package's section body bumped
|
||||||
|
* one heading level deeper (`### Patch Changes` → `#### Patch Changes`).
|
||||||
|
*
|
||||||
|
* Version order: take the order from the package with the most recent
|
||||||
|
* release (the one whose top version is highest by semver). Any extra
|
||||||
|
* versions found only in other packages are appended in semver-descending
|
||||||
|
* order at the end.
|
||||||
|
*/
|
||||||
|
function syncRootChangelog() {
|
||||||
|
const pkgsDir = "packages";
|
||||||
|
const pkgDirs = readdirSync(pkgsDir).filter((name) => {
|
||||||
|
const p = join(pkgsDir, name);
|
||||||
|
return statSync(p).isDirectory() && existsSync(join(p, "CHANGELOG.md"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// { pkgName, versions: Map<versionKey, bodyMarkdown>, order: versionKey[] }
|
||||||
|
const parsed = pkgDirs.map((dir) => {
|
||||||
|
const path = join(pkgsDir, dir, "CHANGELOG.md");
|
||||||
|
const raw = readFileSync(path, "utf8");
|
||||||
|
let pkgName = dir;
|
||||||
|
const titleMatch = raw.match(/^# ([^\n]+)\n/);
|
||||||
|
if (titleMatch) pkgName = titleMatch[1].trim();
|
||||||
|
return { pkgName, ...parseChangelog(raw) };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pick the canonical version order from whichever package has the highest
|
||||||
|
// top version (typically the public CLI). Other packages contribute any
|
||||||
|
// additional versions at the tail.
|
||||||
|
parsed.sort((a, b) => compareSemver(b.order[0] ?? "0", a.order[0] ?? "0"));
|
||||||
|
const seen = new Set();
|
||||||
|
const versionOrder = [];
|
||||||
|
for (const p of parsed) {
|
||||||
|
for (const v of p.order) {
|
||||||
|
if (!seen.has(v)) {
|
||||||
|
seen.add(v);
|
||||||
|
versionOrder.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = [
|
||||||
|
"# Fusion changelog",
|
||||||
|
"",
|
||||||
|
"User-facing release notes aggregated across all packages. This file is auto-synced from each `packages/*/CHANGELOG.md` by `scripts/release.mjs` — do not edit by hand.",
|
||||||
|
"",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const version of versionOrder) {
|
||||||
|
lines.push(`## ${version}`, "");
|
||||||
|
// Sort packages alphabetically within a version for deterministic output.
|
||||||
|
const pkgsForVersion = parsed
|
||||||
|
.filter((p) => p.versions.has(version))
|
||||||
|
.sort((a, b) => a.pkgName.localeCompare(b.pkgName));
|
||||||
|
for (const p of pkgsForVersion) {
|
||||||
|
const body = p.versions.get(version).trim();
|
||||||
|
if (!body) continue;
|
||||||
|
lines.push(`### ${p.pkgName}`, "");
|
||||||
|
// Bump heading levels by one so package sub-sections nest cleanly.
|
||||||
|
const bumped = body.replace(/^(#{1,5}) /gm, (_m, hashes) => `${hashes}# `);
|
||||||
|
lines.push(bumped, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync("CHANGELOG.md", lines.join("\n").replace(/\n{3,}/g, "\n\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a changeset-format CHANGELOG into { versions, order }.
|
||||||
|
* Splits on top-level `## ` headings; the version key is the heading text
|
||||||
|
* verbatim (e.g. "0.2.5", or "0.4.0 (pre-release, unpublished)").
|
||||||
|
*/
|
||||||
|
function parseChangelog(raw) {
|
||||||
|
const versions = new Map();
|
||||||
|
const order = [];
|
||||||
|
// Strip out the first-line title and any horizontal rules so they don't
|
||||||
|
// pollute the first version section.
|
||||||
|
const stripped = raw.replace(/^# [^\n]*\n?/, "").replace(/^---\s*$/gm, "");
|
||||||
|
const sections = stripped.split(/^## /m).slice(1); // drop pre-first-version preamble
|
||||||
|
for (const section of sections) {
|
||||||
|
const nl = section.indexOf("\n");
|
||||||
|
const key = (nl === -1 ? section : section.slice(0, nl)).trim();
|
||||||
|
const body = nl === -1 ? "" : section.slice(nl + 1).trim();
|
||||||
|
if (!versions.has(key)) {
|
||||||
|
versions.set(key, body);
|
||||||
|
order.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { versions, order };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Compare two semver-ish version strings ("0.2.5", "0.4.0 (pre-release)"). */
|
||||||
|
function compareSemver(a, b) {
|
||||||
|
const pa = parseVersionKey(a);
|
||||||
|
const pb = parseVersionKey(b);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
if (pa[i] !== pb[i]) return pa[i] - pb[i];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseVersionKey(key) {
|
||||||
|
const m = key.match(/^(\d+)\.(\d+)\.(\d+)/);
|
||||||
|
if (!m) return [0, 0, 0];
|
||||||
|
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
||||||
|
}
|
||||||
|
|
||||||
async function confirm(prompt) {
|
async function confirm(prompt) {
|
||||||
if (AUTO_YES || DRY_RUN) return true;
|
if (AUTO_YES || DRY_RUN) return true;
|
||||||
const rl = createInterface({ input: stdin, output: stdout });
|
const rl = createInterface({ input: stdin, output: stdout });
|
||||||
@@ -95,6 +208,10 @@ const cliPkg = JSON.parse(readFileSync("packages/cli/package.json", "utf8"));
|
|||||||
const version = cliPkg.version;
|
const version = cliPkg.version;
|
||||||
ok(`New version: ${version}`);
|
ok(`New version: ${version}`);
|
||||||
|
|
||||||
|
info("Syncing root CHANGELOG.md from packages/cli/CHANGELOG.md…");
|
||||||
|
syncRootChangelog();
|
||||||
|
ok("Root CHANGELOG.md updated.");
|
||||||
|
|
||||||
// --- Build ----------------------------------------------------------------
|
// --- Build ----------------------------------------------------------------
|
||||||
|
|
||||||
info("Building all packages…");
|
info("Building all packages…");
|
||||||
|
|||||||
Reference in New Issue
Block a user