2e4fcfcaea09b9dcbccd4443e2f67eb6fed04487
2281 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2e4fcfcaea |
fix(FN-7952): establish PostgreSQL core authority (#2108)
## Summary Fusion’s core runtime now treats PostgreSQL as the authoritative metadata store without leaving current CLI, dashboard, desktop, or engine composition roots uncompilable between stack layers. This is the 99-file foundation for the larger cutover: subsequent PRs migrate the remaining consumers, plugins, and operator surfaces. ## Design decisions - Runtime store construction fails closed when an asynchronous PostgreSQL layer is unavailable; SQLite remains readable only at explicit migration and identity-recovery boundaries. - Project ownership is enforced across active, archived, workflow, mission, analytics, and plugin-schema data. - The small set of cross-package files in this layer are compatibility-critical call sites required for a green intermediate commit, not the complete consumer migration. - Schema migration 0008 remains assigned to session-advisor state from current `main`; mission lineage idempotency advances to 0009 so neither invariant can be skipped. ## Validation - All affected package typechecks pass: Core, Engine, Dashboard, CLI, and Desktop. - `pnpm test:gate` passes: 478 tests across the engine gate, PostgreSQL core gate, and CLI workflow shape. - The PR changes exactly 99 files. ## Stack This is the base PR. Engine/dashboard, CLI/desktop/ops, plugins, and docs/release follow as stacked PRs, each below 100 changed files. Related: #2105 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * PostgreSQL is now the standard runtime backend, with embedded PostgreSQL enabled by default. * Added project-scoped storage for tasks, archives, chat sessions, missions, knowledge pages, and operational data. * Improved archived-task search, filtering, pagination, and restoration. * Added safer plugin schema initialization with validation and project isolation. * Added PostgreSQL-backed workflow, mission, validator, and dashboard capabilities. * **Bug Fixes** * Improved startup timeout cancellation and resource cleanup. * Prevented cross-project data access and phantom reservation cleanup errors. * Ensured archived tasks remain read-only and asynchronous writes complete reliably. * Retired SQLite opt-out settings with clear startup errors. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
f4e78abeb7 |
fix: resolve CREATE ROLE fusion_runtime race condition in migration 0006 (#2104)
## Summary Fixes `CREATE ROLE fusion_runtime` race condition in migration `0006_project_ownership.sql` that causes 30 compound-engineering test failures on CI. ## Root Cause Concurrent test databases on the same PostgreSQL service container race on `CREATE ROLE fusion_runtime`: the `IF NOT EXISTS` check is not atomic (roles are cluster-wide, not per-database). Between the check and the `CREATE ROLE`, another session can create the role, causing error `23505` (unique_violation). ## Fix Replace the non-atomic `IF NOT EXISTS` guard with a `BEGIN...EXCEPTION WHEN duplicate_object OR unique_violation THEN NULL; END;` block that safely handles the race. ## Verification | Check | Result | |---|---| | compound-engineering (pipeline-store + orchestrator + session-routes) | ✅ 41 passed | | Engine shard 1/2 | ✅ 3826 passed, 0 failed | | Merge gate | ✅ 471 passed | | Lint | ✅ exit 0 | |
||
|
|
7568705244 |
fix(desktop): boot embedded Postgres in packaged app and ship omp dist (#2106)
## Summary Packaged Fusion desktop Local mode failed after the SQLite→Postgres cutover: 1. **Embedded Postgres** could not start from `app.asar` — platform packages resolve `initdb`/`postgres` via `import.meta.url` into the asar virtual path, and `spawn` fails with `ENOTDIR`. 2. **After Postgres was fixed**, Local mode still fell back to the mode chooser because `@fusion-plugin-examples/omp-runtime` was never built into `dist/` (dashboard imports it from `runtime-provider-probes.ts`). This PR makes packaged Local mode boot embedded Postgres reliably and keep the dashboard shell up. ### Changes - **CJS bootstrap** (`main-bootstrap.cjs`) as Electron `main`: patches `child_process.spawn` / `fs.promises.stat|chmod` before the ESM main loads so asar binary paths rewrite to real files. - **Materialize** the full native PG install (`bin` + `lib` + `share`) under `~/.fusion/embedded-postgres/runtime-bin/<plat-arch>/`. - **electron-builder**: full `asarUnpack` of embedded-postgres packages; allowlist PG deps and `@fusion-plugin-examples/**/*` (+ plugin-sdk / ACP SDK). - **Build** `fusion-plugin-omp-runtime` with the other dashboard-static runtime plugins; export `DASHBOARD_RUNTIME_PLUGIN_PACKAGES` for tests. - Unit coverage for asar path rewrite, packaging allowlists, and omp build inclusion. ## Test plan - [x] `pnpm --filter @fusion/core test:embedded-postgres` (23/23) - [x] Desktop packaging unit tests (`build-bundling`, `electron-builder-config`) - [x] Packaged macOS `Fusion.app` Local mode: - [x] `embedded postgres: ready on port … (database "fusion")` - [x] `desktopMode` stays `"local"` (no chooser fallback) - [x] `GET /api/health` → `status: ok`, `database.healthy: true`, `engine.available: true` - [x] Linux embedded binary lifecycle smoke (Docker aarch64, `@embedded-postgres/linux-arm64`) — initdb/start/persist/restart - [ ] CI release desktop jobs (macOS/Linux) when this lands - [ ] Windows packaged desktop Local + PG (separate agent / host) ## Verification notes | Platform | Embedded Postgres | Packaged Local shell | |----------|-------------------|----------------------| | macOS | Working | Working after this PR | | Linux | Native binary smoke pass | Full AppImage not built on this host | | Windows | Out of scope here | Separate verification | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved embedded PostgreSQL reliability in Electron-packaged apps by rewriting bundled `app.asar` binary paths to their unpacked/materialized locations. * Ensured embedded PostgreSQL runtime binaries resolve correctly across platforms/architectures, with best-effort executable permissions and macOS dylib link normalization. * **Packaging** * Updated the desktop Electron entry to use a bootstrap module for embedded PostgreSQL binary resolution. * Expanded Electron Builder inclusion and asar-unpack rules for embedded-postgres and related packages, plus required runtime plugin/sdk assets. * **Tests** * Updated and added checks to match the new packaging and plugin/runtime expectations. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
4f037679ad |
feat: planner overseer session advisor (OMP advisor parity) (#2082)
## Summary Adds a **session advisor** to the planner overseer so Fusion can review live executor transcripts the way [oh-my-pi’s advisor](https://github.com/can1357/oh-my-pi/tree/main/packages/coding-agent/src/advisor) does — without replacing the existing lifecycle supervisor (stage watch, retry, merge confirmation, human-control withhold). ### What ships - **Emission guard** (`OverseerEmissionGuard`) — content-free phrase filter, session dedupe with severity-rank escalation, one accept per advisor update - **Session delta runtime** — queues agent-log deltas, drains through an advisor agent, drops backlog after 3 failures - **Session advisor service** — model gate, level matrix (`observe` / `steer` / `autonomous`), human-control re-check at inject, `[session-advisor]` steering comments - **OVERSEER.md / WATCHDOG.md** discovery for project review priorities - **AgentLogger `onEntriesFlushed`** + poll-backed agent-log cursor for durable deltas - Workflow settings: `plannerOverseerAdvisorProvider` + `plannerOverseerAdvisorModelId` (both required; empty = soft-disabled for cost safety) - Docs + changeset ### What does not ship (deferred) - Multi-advisor YAML roster, mutating advisor tools, reviewer/merger shadowing, true tool-abort interrupt ### Plan `docs/plans/2026-07-13-001-feat-overseer-advisor-parity-plan.md` ## Enablement 1. Set workflow **Session advisor model provider** + **Session advisor model id** 2. Oversight level `observe` (log only), `steer`, or `autonomous` (inject) 3. Optional: add `OVERSEER.md` or `WATCHDOG.md` in the project ## Test plan - [x] `pnpm --filter @fusion/core exec vitest run src/__tests__/overseer-emission-guard.test.ts` - [x] `pnpm --filter @fusion/engine exec vitest run` overseer-* unit tests (21 tests) - [x] Related planner-overseer / intervention regression tests - [x] `@fusion/engine` + `@fusion/core` typecheck - [ ] Manual: configure advisor model, run an executor task, confirm `[session-advisor]` inject + timeline metadata when concern is raised ## Residual Review Findings None from autofix pass (log-cursor ordering fix already committed). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an off-by-default “session advisor” that can review live execution activity and provide severity-based guidance. * Added project and per-task controls to enable it, including a default enable switch and Quick Add / Task Detail toggles. * Enhanced advisor prompting by discovering and incorporating `OVERSEER.md`/`WATCHDOG.md` review files. * **Documentation** * Added architecture and settings documentation for the new session-advisor parity behavior. * **Bug Fixes** * Improved fail-soft handling so advisor behavior won’t disrupt execution. * Fixed concurrent PostgreSQL migration startup failures. * **Tests** * Added coverage for advice parsing, emission guarding, runtime behavior, and watchdog discovery. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
cdf67c1d98 |
fix(dashboard): stop Planning Mode retry loop, make AI sessions multi-tab (#2101)
## Problem Reported: planning gets stuck in a cycle of retrying and regenerating after a response was already supplied. After the user answers a planning question, `submitResponse` pushed the answer to history but left `session.currentQuestion` pointing at the just-answered question for the whole next generation. The planning SSE route's catch-up path re-emits `currentQuestion` to every fresh connection — and each FN-7946 auto-retry (#2073) opens a fresh connection. So after any generation error: 1. Auto-retry connects a fresh stream → the server re-emits the **already-answered** question. 2. The client treats any question event as progress: it **resets the 3-attempt auto-retry budget** and re-shows the answered question. 3. The retry regenerates; if it errors again the cycle repeats with a fresh budget — an unbounded retry/regenerate loop. Re-answering the stale question also 409-collided with the in-flight generation, feeding the same loop. ## Fix Invariant: `currentQuestion` is only set while the session is genuinely awaiting user input. - `submitResponse` clears it the moment an answer is accepted (normal turns and the deepening checkpoint), while preserving the legacy 200 respond contract on generation failure (the modal ignores the body and lets the SSE error drive recovery). - `retrySession` scrubs stale questions persisted by pre-fix builds before regenerating. - `buildSessionFromRow` only restores a question when the persisted row is `awaiting_input`. - `didSubmitSameAnswer` now compares against the last history entry so the duplicate-submit 409 message survives. - Agent onboarding gets the same fix (its SSE route also re-emits `currentQuestion` on connect); retry now asks the next question instead of re-asking the answered one. Surface enumeration: mission and milestone interviews keep questions the same way but their SSE routes never re-emit on connect, and the auto-retry budget machinery is Planning-Mode-only — planning + onboarding were the two affected surfaces. ## Symptom Verification - **Original symptom:** after answering a question, Planning Mode loops between "Retrying…" and regenerating, re-showing the already-answered question, with the auto-retry budget never exhausting. - **Exact reproduction:** answer a question, have the next generation fail (stuck watchdog/provider error), let the client auto-retry open a fresh SSE connection. - **Assertion it is gone:** new regression suite `planning-answered-question-reemit.test.ts` asserts `currentQuestion` is cleared mid-generation, on generation failure, on retry, and on restore from non-`awaiting_input` rows — so the SSE catch-up path has nothing stale to re-emit. All 5 tests fail against pre-fix code and pass with the fix; an onboarding regression test covers the sibling surface. ## Verification - New regression tests: 5/5 fail on pre-fix code, pass with the fix (plus 1 onboarding test). - Existing suites: 137 planning server tests pass (3 failures in `routes-planning.test.ts` fail identically without this change — pre-existing on the branch); all 69 `PlanningModeModal.planning-flow` client tests pass; `tsc --noEmit` clean; `pnpm check:changesets` passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Made Planning Mode (and related planning controls) lock-free and multi-tab—no more take-over/active-in-another-tab lock overlays. * **Bug Fixes** * Fixed Planning Mode retry/generation flows where already-answered questions could reappear. * Ensured answered questions clear immediately and aren’t re-emitted during session recovery/SSE catch-up. * Improved session restoration and preserved legacy recovery behavior when generation fails after an answer. * **Tests** * Added regression coverage for the answered-question invariant and updated existing tests to reflect lock-free behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --- ## Follow-up: Planning Mode is now multi-tab via DB state (lock-free) Second commit removes all cross-tab coordination from planning — the persisted session row is the single source of truth and multiple tabs can read and interact with the same session: - **Server:** `/planning/*` routes no longer run `checkSessionLock` or parse `tabId`; a stale `tabId` from an older client is ignored instead of 409'd. Subtask/mission interview routes keep their existing lock behavior. - **Client:** `PlanningModeModal` drops `useSessionLock`, the `useAiSessionSync` BroadcastChannel broadcasts, `sessionTabId`/`lockSessionId` state, and the "Take Control" overlay. Tabs stay current via the per-session SSE stream plus the global `ai_session:updated` events `useBackgroundSessions` already consumes; concurrent writes resolve via the server's generation-in-progress guard (409). - **API client:** planning functions lose their `tabId` params. - **Fix uncovered by the refactor:** the 8s stuck-poll now resolves the session id inside each tick — the removed lock state was what previously re-armed the poll after Start Planning resolved the session id. - Also fixes a pre-existing PG-cutover break in `planning-generation-cancellation.test.ts` (`getSession` is async). Verification: 144 client planning tests and 137 server planning tests pass (the 3 remaining `routes-planning.test.ts` failures are pre-existing on the branch and fail identically without these changes); `tsc --noEmit` and eslint clean on changed files; `pnpm check:changesets` passes. Lock-conflict route tests were rewritten to assert lock-free semantics, plus a new modal test proving a session stays fully interactive with no lock acquisition even when another tab is active. --- ## Follow-up 2: the per-tab session lock is gone entirely Third commit extends the multi-tab model from planning to **every** AI interview surface (planning, subtask breakdown, mission interview, milestone/slice interview) and deletes the lock machinery root and branch. **Server** - Deleted the `/ai-sessions/:id/lock`, `/lock/force`, and `/lock/beacon` routes. - Dropped `checkSessionLock` from every planning/subtask/mission/milestone route (both copies — `routes.ts` and `mission-routes.ts`). A `tabId` from an older client is ignored, never 409'd; all `tabId` body parsing is gone. - Dropped `acquireLock` / `releaseLock` / `forceAcquireLock` / `getLockHolder` / `releaseStaleLocks` from `AiSessionStore`, plus the `@fusion/core` async helpers (`acquireAiSessionLock` et al) and core's re-exports. - Removed `lockedByTab`/`lockedAt` from `AiSessionRow`/`AiSessionSummary`, the upsert SQL, and all four session producers. **Client** - Deleted `useSessionLock` and the now-orphaned `getSessionTabId` util. - Removed the Take Control overlay, the "active in another tab" banners, and `BackgroundTasksIndicator`'s active-elsewhere gate (the confirm prompt and lock badge — sessions now just open). - Reduced `useAiSessionSync` to what its own comments already called it — a low-latency *status* supplement to SSE: no `activeTabMap`, `broadcastLock/Unlock/Heartbeat`, `owningTabId`, `tab:*` messages, or stale-heartbeat sweep. - Dropped `tabId` from every session API client function; removed the lock CSS. **Deliberately kept: the two DB columns.** `ai_sessions.locked_by_tab` / `locked_at` remain as dead, always-NULL columns with a deprecation note. Dropping them is an irreversible migration, and released binaries still name those columns explicitly in their upsert — an older install pointed at the same database would fail every session write. They can be dropped once no such binary can reach it. No code reads or writes them. **Verification**: 397 client tests and 137 server planning tests pass (the same 3 `routes-planning.test.ts` failures are pre-existing — verified identical on a clean stash); `tsc --noEmit` clean for `@fusion/core` and `@fusion/dashboard`; eslint clean on all changed files; the 30 PG `schema-applier` tests pass (they exercise the retained columns); `pnpm check:changesets` passes. The lock-conflict route tests and both modal lock tests were rewritten to assert the inverse: routes and modals stay fully interactive while another tab "holds" a lock, and the lock API is never called. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
be55d0a987 |
fix(cli): reuse project stores for skill discovery (#2102)
## Summary - reuse the dashboard command's backend-aware per-project `TaskStore` cache during project-scoped plugin skill discovery - obtain plugin state through `TaskStore.getPluginStore()` instead of constructing bare SQLite-default `PluginStore` / `TaskStore` instances - keep cached project stores alive for the dashboard process while still stopping request-scoped plugin loaders - add a regression covering the real Skills adapter callback and refresh the dashboard test fixture with `getAsyncLayer()` ## Root cause `GET /api/skills/discovered` resolved the project correctly, then `getProjectScopedPluginSkills()` constructed new stores without an `AsyncDataLayer`. After `VAL-REMOVAL-005`, that enters the physically removed synchronous SQLite runtime and returns HTTP 500 even when PostgreSQL health, projects, tasks, and both project engines are healthy. The existing route tests mocked the Skills adapter callback, so they did not exercise this CLI wiring. ## Verification - targeted dashboard regression: 1 passed, 91 skipped - `pnpm lint` - `pnpm --filter @runfusion/fusion typecheck` - `pnpm --filter @runfusion/fusion build` - `pnpm check:changesets --strict` - `git diff --check` Live Atlas validation against the migrated embedded PostgreSQL runtime: - `/api/skills/discovered?projectId=proj_84f4645c2da64288`: HTTP 200, 36 skills - `/api/skills/discovered?projectId=proj_7538a9dd46c24c5f`: HTTP 200, 36 skills - local dashboard and Tailscale dashboard: HTTP 200 - controlled SIGTERM: launchd restarted the dashboard and both Skills routes remained healthy <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Fixed dashboard project-scoped plugin-skill discovery in PostgreSQL mode with safer store reuse/teardown and request-scoped plugin-loader lifecycle. - Improved dashboard cleanup to avoid duplicate concurrent store closes and ensured proper shutdown behavior per root type. - Made `fusion_runtime` role creation race-safe during concurrent PostgreSQL migrations. - **New Features** - Added `persistRuntimeState` option to control whether plugin runtime state changes are persisted. - **Tests** - Expanded dashboard and core hot-reload tests to verify scoped, non-persistent runtime behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
2d61976df0 |
fix(FN-7952): restore runtime state after PostgreSQL migration
Route workflow selections, model lanes, goals, skills, and reliability reads through project-scoped async stores. Recover heartbeat agents parked against an unrelated project model and preserve workflow JSONB patches atomically. |
||
|
|
678265a526 |
fix(cli): show live SQLite migration progress
Report source scans, per-table copy milestones, checksum phases, verification outcomes, and unambiguous failure or finalization status during first-boot and manual migrations. |
||
|
|
0312d2e140 | fix(core): preserve late SQLite columns during cutover | ||
|
|
7677ab07dc |
fix: add chat_sessions columns to schema baseline + fix remaining PG auth bugs (shard 4) (#2096)
## Summary Fixes shard 4 full-suite failures: chat_sessions schema baseline gap + two remaining PG auth bugs missed by PR #2086. **Scope: shard 4 only.** Shards 1/2 (engine timeouts) and shard 3 (compound-engineering CI-only failure) are separate issues not addressed here. ## Changes ### Schema baseline gap — `chat_sessions` missing columns (42703 error) - **`0000_initial.sql`**: Added `validator_thinking_level` and `planning_thinking_level` columns to `CREATE TABLE project.chat_sessions`. These exist in the Drizzle schema (`project.ts:1492-1493`) but were missing from the SQL baseline, causing `column does not exist` on all chat_sessions inserts in fresh test databases. - **`postgres-health.ts`**: Added both columns to `EXPECTED_PROJECT_COLUMNS` self-heal list so existing databases also get them via ALTER TABLE. **Fixes**: `chat-store-content-search-edit.pg.test.ts` (5 tests), `satellite-db-injected-stores.test.ts` (2 tests) ### Remaining auth bugs (password auth failed for user "runner") - **`allocator-cross-project.test.ts`**: Still had `process.env.USER` in inline adminExec — missed by PR #2086's batch fix. Replaced with `PG_TEST_URL_BASE` connection string. - **`connection.test.ts`**: Used `FUSION_PG_TEST_URL` (not set on CI) with a bare default URL lacking credentials. `postgres.js` fell back to OS user `runner`. Changed to derive from `FUSION_PG_TEST_URL_BASE` which includes credentials. **Fixes**: `allocator-cross-project.test.ts` (2 tests), `connection.test.ts` (3 tests) ## Verification | Check | Result | |---|---| | Merge gate (`pnpm test:gate`) | ✅ 294 + 114 + 63 = 471 passed | | chat-store-content-search-edit | ✅ 5 passed | | satellite-db-injected-stores | ✅ 10 passed | | allocator-cross-project | ✅ 2 passed | | connection | ✅ 13 passed | | Lint | ✅ exit 0 | | Typecheck | ✅ clean | ## Not in scope - **Shards 1/2**: Engine test suite timeouts with `getAsyncLayer`/`updateSettings` mock warnings. Pre-existing. - **Shard 3**: `compound-engineering stage-skill-loading.test.ts` — 14 tests fail on CI (`TypeError: Cannot read properties of undefined (reading 'close')`), pass locally. Likely CI-specific teardown issue. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added separate `validator_thinking_level` and `planning_thinking_level` fields to chat session data, including database schema and health-check recognition. * **Bug Fixes** * Improved PostgreSQL test connectivity by using configured connection URL settings instead of hardcoded local defaults. * Made Postgres-related test teardown null-safe to avoid failures when setup doesn’t complete. * **Tests** * Updated automated test quarantine/exclusions for known failing engine and reliability-interaction cases. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
379d450c38 |
fix(core): preserve required empty JSON during migration (#2099)
## Summary - preserve empty and whitespace-only legacy SQLite text as JSON string scalars when the PostgreSQL target is required `jsonb` without a default - keep nullable/defaulted JSON behavior unchanged - canonicalize converted JSON before source/target checksum comparison - cover empty, whitespace, malformed, and scalar workflow IR values ## Test plan - `FUSION_PG_TEST_URL_BASE=postgresql://127.0.0.1:55432 nix shell nixpkgs#postgresql_15 -c bash -c 'corepack pnpm --filter @fusion/core exec vitest run src/__tests__/postgres/sqlite-migrator.test.ts -t "preserves empty, whitespace, malformed, and scalar values" --reporter=dot'`\n- `corepack pnpm --filter @fusion/core typecheck`\n- `corepack pnpm check:changesets --strict`\n- `corepack pnpm --filter @runfusion/fusion build` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved SQLite-to-PostgreSQL migrations for required `jsonb` fields. - Preserves empty, whitespace-only, malformed, and scalar JSON values instead of replacing them with defaults or `NULL`. - Maintains existing `nullable` and default-value behavior. - Improved migration verification for converted `jsonb` data. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
945d629e3b |
fix(core): make SQLite cutover lossless and project-local
Preserve legacy-only tables, recover partial migration ownership, and enforce project-local keys, relationships, agents, merge queues, task IDs, archives, and monitor state with PostgreSQL RLS. Report successful cutovers once in the dashboard and system inbox with retained SQLite paths and Discord support details. |
||
|
|
7c8a84fb2f |
fix(core): converge multi-project SQLite cutover
Migrate central SQLite state once per cluster, isolate project metadata, and preserve file-local revision identities while verifying accumulated shared tables. |
||
|
|
1b9dce22c0 | fix(desktop): harden embedded Postgres packaging | ||
|
|
12a4fbe9bb | fix(core): complete legacy SQLite cutover | ||
|
|
99870ba329 | fix(core): recover partial PostgreSQL migrations | ||
|
|
dff864e098 |
feat: harden permanent-agent heartbeat instructions (#2081)
## Summary Hardens permanent-agent operating law while keeping the heartbeat/executor split: - **Critical Rules** in task-scoped and no-task heartbeat system prompts (survive custom `HEARTBEAT.md`) - Stronger default procedures: disposition checklist, scoped-wake, blocked dedup, progress note style - **Wake Delta multi-assign inventory** (ranked, cap 8, coordination-only framing) + `checkout_conflict` regression test - Standing instructions six-section template for blank custom create / empty detail insert - Onboarding interview guidance to prefer structured `instructionsText` - Playbooks, CONCEPTS, agents.md accuracy; remove stale agent gap-analysis doc Plan: `docs/plans/2026-07-12-001-feat-permanent-agent-heartbeat-instructions-plan.md` ## Test plan - [x] `pnpm --filter @fusion/core exec vitest run src/__tests__/assigned-task-ranking.test.ts` - [x] `pnpm --filter @fusion/engine exec vitest run src/__tests__/agent-heartbeat-procedures.test.ts src/__tests__/heartbeat-executor.test.ts -u` - [x] `pnpm --filter @fusion/dashboard exec vitest run app/components/__tests__/standing-instructions-template.test.ts` - [ ] CI gate green on PR ## Residual Review Findings None recorded at open (inline review; no residual sink). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added ranked multi-assignment context to agent heartbeat wake-ups, including task status, ownership, and lease details. * Added standing-instructions templates for creating and editing permanent agents. * Improved onboarding guidance with a consistent six-section instruction structure. * Added clearer heartbeat handling for blocked tasks, no-task runs, and checkout conflicts. * **Documentation** * Added permanent-agent heartbeat playbooks and expanded coordination glossary entries. * Updated documentation indexes and heartbeat behavior guidance. * **Tests** * Added coverage for task ranking, instruction templates, wake-up context, and conflict handling. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
b563b12662 |
feat: add Oh My Pi (omp) ACP runtime plugin (#2083)
## Summary - Add `fusion-plugin-omp-runtime` so Fusion agents can run through operator-installed **Oh My Pi (`omp`)** over the [Agent Client Protocol](https://omp.sh/docs/acp) (`omp acp`). - Wire staged/bundled install, Settings → Authentication card (enable + binary path), model discovery (`omp models` → `omp-cli/*`), and MCP eligibility for runtime id `omp`. - Forward Fusion `systemPrompt` via ACP `session/new` `_meta.systemPromptOverride`. ## How operators use it 1. Install/auth `omp` (credentials under `~/.omp`). 2. Enable **Oh My Pi — via omp ACP** in Settings → Authentication (optional binary path). 3. Set agent **Runtime Source → OMP Runtime** (`runtimeHint: "omp"`), or pick an `omp-cli/*` model when enabled. ## Known v1 gaps - No Grok-style Fusion `fn_*` loopback tool bridge yet (operator MCP is forwarded; in-process custom tools are not). - Model is fixed at spawn (`omp --model … acp`); no mid-session Fusion model switch. ## Test plan - [x] `pnpm --filter @fusion-plugin-examples/omp-runtime test` (unit + live ACP when `omp` is on PATH) - [x] Auth routes: `POST /api/auth/omp-cli`, `GET /api/providers/omp-cli/status` - [x] Engine `runtimeSupportsMcp("omp")` - [ ] Manual: enable card in dashboard, select OMP runtime on an agent, run a short chat turn <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added Oh My Pi (OMP) CLI support as an ACP-backed runtime and model provider, including model discovery and probing. * Added dashboard auth/status controls to enable OMP, check readiness, and configure the local binary path (with validation). * Exposed OMP custom `fn_*` tools via an MCP loopback bridge, plus optional filesystem capabilities and stricter tool permission gating. * **Documentation** * Added/expanded OMP runtime contract and integration docs (including the ACP session/handshake flow). * **Tests** * Added Vitest coverage for settings wiring, provider status, model discovery, runtime sessions, permissions, MCP bridging, and live connectivity. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
c25f8b796d |
Harden PostgreSQL migration foundation (#2088)
## Summary - make SQLite-to-PostgreSQL cutover retryable, fail-closed, versioned, and transactionally serialized - isolate migration sessions from runtime traffic and apply schema upgrades through `0002` - enforce tenant ownership across automations, analytics, activity, usage, agent runs, evals, and todos - replace expired SQLite-only coverage with PostgreSQL parity and concurrency coverage This is PR 1 of 2. The stacked follow-up restores PostgreSQL parity for CLI, engine, dashboard, and bundled integrations. ## Verification - `pnpm check:changesets --strict` - `pnpm --filter @fusion/core typecheck` - migration schema, connection, and SQLite cutover suite: 57 tests passed - `pnpm test:gate`: 463 tests passed ## Post-Deploy Monitoring & Validation - take a restorable PostgreSQL backup before deploy - confirm `fusion_schema_migrations` contains `0002` - confirm each expected project has a complete `fusion_sqlite_migrations` row - verify no null or empty tenant ownership in automations, activity logs, agent runs, and usage events - monitor for ownership inference failures, cutover verification failures, and migration session errors - restore the backup for data rollback; do not downgrade the tenant-isolation schema in place <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * PostgreSQL-backed analytics and live dashboard metrics are now project-scoped (activity, tools, monitor, signals, and live snapshots). * Evaluation runs and scheduled eval batches received lifecycle improvements (ordering, updates, and execution flow). * Todo list changes now emit events; WhatsApp persistence and project-scoped roadmap data are supported. * **Bug Fixes** * SQLite-to-PostgreSQL cutovers now fail safely with stronger verification, serialized cutover handling, and safer project ownership. * PostgreSQL backend writes and reads are now strictly project-isolated and fail closed when project context is missing. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
bc348345a4 |
fix(engine): break Plan Review REVISE replan loop (feedback + bounded cap) (#2078)
## Problem A task whose Plan Review step returns verdict `REVISE` can loop forever: plan → plan-review REVISE → `needs-replan` → re-plan → near-identical plan → REVISE → repeat. The triage **pre-execution** Plan Review gate (`runPlanReviewBeforeExecution`) sets `status: "needs-replan"` on REVISE with **no cap and no escape to `awaiting-approval`** — unlike the executor graph path, which already has `PLAN_REVIEW_REPLAN_HARD_CAP`. Under `planApprovalMode: require-all` there is also no human exit, because the task never reaches `awaiting-approval`. Separately, replan feedback (`triage.ts`) was derived only from `task.log` comment actions + the latest user comment; it never consulted the plan-review verdict stored in `task.workflowStepResults`. ## Fix 1. **Thread plan-review feedback into replan** — when re-planning with no comment-derived feedback, seed `buildSpecificationPrompt` from the most recent `plan-review` REVISE `output` in `workflowStepResults` (existing user/AI-comment precedence preserved). 2. **Bounded cap** — new `planReviewReplanCount` counter (`types.ts`, `store.ts` column + updateTask, `db.ts` migration 146, `manual-retry-reset.ts`). After `PLAN_REVIEW_GATE_REPLAN_CAP = 3` consecutive REVISE replans the task escalates to `awaiting-approval` (`awaitingApprovalReason: "plan-review-replan-cap"`) instead of replanning. Counter resets on APPROVE. ## Tests Adds `triage-replan-feedback-from-plan-review.test.ts` and `triage-plan-review-replan-cap.test.ts`. Merge gate green locally (`verify:fast`, `test:gate` 337+63, `lint`); changeset included. Made with Claude (see `Co-Authored-By` trailer). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Prevented Plan Review “REVISE” from looping indefinitely by enforcing a bounded replan cap. * After repeated Plan Review replans, tasks now escalate to an approval-hold state with a dedicated reason. * Improved replan feedback by seeding from the latest Plan Review output when no explicit feedback is available; the counter clears when Plan Review approves. * Manual retries now reset the Plan Review replan cap counter. * **Documentation** * Added release notes describing the Plan Review replan safeguards. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> |
||
|
|
8de0fbcd01 |
fix: repair full-suite failures after SQLite-to-PostgreSQL cutover (#2086)
## Summary
Fixes all deterministic full-suite (non-blocking) CI failures on `main`
caused by the SQLite-to-PostgreSQL cutover (VAL-REMOVAL-005).
## Changes
### i18n Key Parity (5 locale files)
- Added missing `taskPopupsBoardListOnly` +
`taskPopupsBoardListOnlyHelp` keys (empty strings per convention) to
zh-CN, zh-TW, fr, es, ko `app.json`
### Dashboard Curated-Gate Guard (`scripts/lib/test-quarantine.json`)
- Repaired "mirror drift": 16 dashboard test files were quarantined in
`vitest.config.ts` but never added to the quarantine ledger. Added all
16 with failing run URLs and `quarantinedAt` dates.
### Line-Count Audit CI Cache (`.github/workflows/full-suite.yml`)
- Removed `skip-install: "true"` from `line-count-audit` job —
`setup-node@v5` with `cache: pnpm` failed post-step because no
`node_modules` existed to cache.
### Engine Slow Tier — Full PG Migration
- **CI**: Added PostgreSQL service container to `test-slow` job (same
config as `test-shards`)
- **`_helpers.ts`**: Migrated `makeReliabilityFixture()` from removed
SQLite `Database.init()` to PG-backed `TaskStore`:
- Added `probeTcpReachable()` (TCP probe, copied from shared harness)
- Added `hasPg` export (uses TCP probe, not env-var guess)
- Added `adminExecAsync()` (`Promise.withResolvers`, psql via
`PG_TEST_URL_BASE`)
- Added `createPgLayer()` (fresh PG database + schema baseline +
`AsyncDataLayer`)
- Updated cleanup: `await store.close()`, close layer, drop database
- **Slow test**: Migrated 24 sync SQLite API calls to async PG APIs:
- `store.getRunAuditEvents()` → `await auditEvents(store, ...)` via
exported `queryRunAuditEvents`
- `store.getDatabase().prepare(...)` → Drizzle queries via
`store.getAsyncLayer()!.db`
- **Core exports**: Added `queryRunAuditEvents` from `async-audit.ts`
and `eq as drizzleEq` from `drizzle-orm`
- **22 reliability test files**: Added `hasPg` guards so tests skip
locally when PG is unavailable
### Shard 3 — PG Test Auth Bug (18 postgres test files)
- Replaced `psql -U ${process.env.USER ?? "postgres"}` with `psql
"${PG_TEST_URL_BASE}/postgres"` connection string. On GitHub Actions,
`process.env.USER` is `'runner'`, not `'postgres'`, causing auth
failure.
### Shard 3 — Removed Function Tests (`mesh-task-replication.test.ts`)
- Deleted 3 tests for functions intentionally removed in PostgresCutover
(`buildMeshReplicatedTaskCreatePayload`, `toReplicatedCreateInput`,
`taskMatchesReplicatedCreate`). Kept `buildBootstrapPrompt` test.
### Shard 3 — Store Thinking Levels (`store-thinking-levels.test.ts`)
- Migrated from removed SQLite path to PG-backed
`createTaskStoreForTest` + `pgDescribe`.
## Verification
| Check | Result |
|---|---|
| Merge gate (`pnpm test:gate`) | ✅ 294 + 99 + 63 = 456 passed |
| Engine slow tier (22 tests) | ✅ 22/22 passed |
| i18n parity tests | ✅ 7 passed |
| mesh-task-replication | ✅ 1 passed |
| PG data-layer | ✅ 14 passed |
| PG taskstore-lifecycle | ✅ 16 passed |
| store-thinking-levels | ✅ 1 passed |
| Dashboard curated-gate | ✅ passes |
| Typecheck (engine + core) | ✅ clean |
| Lint | ✅ exit 0 |
## Parked (not in scope)
- **Shards 1/2 timeout**: Engine test suite exceeds CI time budget.
Pre-existing, unrelated to these fixes.
- **2 latent PG files** (`chat-store-content-search-edit`,
`satellite-db-injected-stores`): Surface a separate pre-existing schema
baseline gap. Out of scope.
|
||
|
|
b5c76af700 |
fix(core): preserve jsonb defaults during PostgreSQL migration (#2080)
## Summary - preserve target defaults when legacy SQLite rows contain `NULL` or empty strings for `NOT NULL` jsonb columns - derive the fallback from PostgreSQL column metadata instead of hard-coding table or column names - keep migration checksum conversion aligned with inserted values - add regression coverage for legacy null JSON fields ## Test plan - `corepack pnpm@10.33.0 --filter @fusion/core typecheck` - `FUSION_PG_TEST_SKIP=1 corepack pnpm@10.33.0 --filter @fusion/core exec vitest run src/__tests__/postgres/sqlite-migrator.test.ts` - `corepack pnpm@10.33.0 --filter @fusion/core build` The PostgreSQL-backed integration suite requires `psql`, which is unavailable in this environment; CI should exercise the added migration case against PostgreSQL. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved SQLite-to-PostgreSQL migration for legacy rows containing `NULL` or empty JSON values. * For eligible `NOT NULL` `jsonb` columns, the migrator now preserves/apply compatible PostgreSQL column defaults instead of writing SQL `NULL`. * Migration verification now aligns with the final values inserted into PostgreSQL to prevent checksum mismatches. * **Tests** * Added an end-to-end legacy migration case to confirm `jsonb` fields materialize as empty defaults (e.g., `[]`) rather than staying `NULL`. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
8e4514e585 |
fix: key workflow settings by the central project id and stamp all partitioned tables on both migration paths
Closes the remaining PG-cutover partitioning gaps: - getWorkflowSettingsProjectId resolves the bound AsyncDataLayer's central- registry id first. In backend mode the SQLite stub's getProjectIdentity() throws, so the old fallback ALWAYS keyed workflow_settings / workflow_prompt_overrides by the rootDir path string — a namespace nothing else reads, making workflow settings appear reset after cutover. - Stamping is extracted into core stampMigratedProjectRows (tasks/archived NULL->id, config ''->id, workflow_settings + workflow_prompt_overrides rootDir-key->id, all guarded against clobbering per-project rows), shared by startup-factory Step 5.5 and 'fn db migrate', which now resolves the registered project by path after the copy and warns when unregistered. - The task-id allocator and merge_queue are verified safe WITHOUT project partitioning: task ids are a global PK, the per-prefix sequence scans are intentionally global (only the per-project config floor can raise them), so two projects sharing a prefix cannot mint duplicate ids. FNXC comments lock the invariant; a cross-project PG regression test proves it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3ccc9f96e6 |
fix: bind rootDir boots to the central project registry and re-key the migrated config row
Implements the central-project-identity architecture: cwd/rootDir is ONLY a
lookup key into central.projects; project identity (the partition key for
every task/config read and write) comes from the registry.
- createTaskStoreForBackend resolves the registered project id by path for
rootDir-only boots and binds the AsyncDataLayer to it. Previously
'fn dashboard' / 'fn serve' / desktop booted their main store UNBOUND, so
unscoped API requests wrote NULL-project_id rows the projectId-bound engine
could never see, and unbound config reads (id = 1) were indeterminate once
multiple per-project rows existed. The engine already worked registry-first
(resolveLocalProjectWorkingDirectory); this brings the store boots in line.
- Step 5.5 auto-migration now also re-keys the migrated legacy config row
('' -> project id, guarded against clobbering an existing per-project row).
configScope() has no bound->'' fallback, so the migrated project settings,
workflowSteps, taskPrefix, and nextId counters were silently invisible to
bound readers right after a successful migration.
- Unregistered paths resolve to undefined and boot unbound, preserving legacy
single-project behavior with unfiltered readers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
0f3a3d3f49 |
fix: stamp migrated task rows with the central-registry project id on rootDir-only boots
The SQLite -> PostgreSQL auto-migration leaves project_id NULL and Step 5.5 only stamped rows when options.projectId was bound — but 'fn dashboard' in the project directory (the main cutover path) boots with rootDir only, so every migrated row stayed NULL, project-bound readers (engine InProcessRuntime, dashboard project-store-resolver) filtered them all out, and the board showed no tasks right after a successful migration. The stamping id is now resolved from the freshly-migrated central registry by matching the registered project path to rootDir; projects never registered centrally keep NULL rows, matching their unbound readers. Integration test covers the rootDir-only stamp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7aa969892a |
fix: count actually-inserted rows in the SQLite -> PostgreSQL migrator via RETURNING
insertBatch read the driver wrapper's count (result.count ?? result.rowCount ?? rows.length), which reported 0 through drizzle's execute even when every row landed — migration reports showed 'inserted 0' for fully-migrated tables and the startup banner's migratedRows total was wrong. ON CONFLICT DO NOTHING RETURNING 1 yields exactly one row per row actually inserted, making the count driver-agnostic and correctly excluding conflict-skipped rows. Idempotency test now asserts first-run insertedRows == sourceRows and re-run insertedRows == 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fbcd00204a |
fix: snake_case legacy SQLite table names in the PG migrator and keep PG-mode boots from touching SQLite
Two post-cutover fixes: 1. The SQLite -> PostgreSQL migrator matched table names verbatim while only column names were snake_cased, so all 22 legacy camelCase tables (activityLog, runAuditEvents, mergeQueue, taskClaims, projectNodePathMappings, ...) resolved zero PostgreSQL columns and were silently skipped as 'no PostgreSQL counterpart'. First observed as 'Project/node path mapping not found' on engine start because central.project_node_path_mappings was never populated. TablePlan now carries a snake_cased pgTable used for every PostgreSQL-side operation; regression test migrates a camelCase activityLog into project.activity_log. 2. The first-boot auto-migration guard opened .fusion/fusion.db with a read-write DatabaseSync on every boot (isValidSqliteDatabaseFile), which performs WAL recovery + checkpoint — writing the legacy file on each PG boot. The PG emptiness count now runs before the SQLite probe, so steady-state PG boots never open the legacy SQLite files at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8596035159 |
fix: bind fallback CentralCore to the async layer so projectId-only boots resolve projects on PostgreSQL
getOrCreateForProjectImpl constructed its fallback CentralCore without the caller's AsyncDataLayer. Post-cutover a layer-less CentralCore has no database at all (the SQLite CentralDatabase path is deleted and init() degrades to a no-op), so project lookups returned empty and every projectId-only boot through the startup factory (engine InProcessRuntime, dashboard project-store-resolver) failed with 'Project "<id>" not found' even though central.projects had the row — dashboard UI came up but the engine never connected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
eb5c81cc59 |
fix: widen ce_sessions.last_activity_at to bigint so PG first-boot migration survives epoch-ms values
project.ce_sessions.last_activity_at stores Date.now() epoch milliseconds but was declared integer in both the Drizzle shape and the CE plugin schema-hook DDL, overflowing PG int4 during the SQLite -> PostgreSQL first-boot auto-migration and blocking startup at task-store init. Now bigint in both sites, with an idempotent ALTER for datadirs that already materialized the integer column, plus a schema-wide invariant test that no numeric *_at/*_time/*_timestamp column is 32-bit integer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c15c78feeb |
feat: migrate storage from SQLite to PostgreSQL (#1793)
# Migrate storage from SQLite to PostgreSQL — full dashboard cutover Migrates Fusion's storage layer to the embedded PostgreSQL `AsyncDataLayer` (the default backend) and **completes the satellite-store + feature cutover** so every dashboard and Command Center surface works in PG mode. ## Status — every surface works in embedded-PG mode Verified live against a running embedded-Postgres dashboard (all **200**, zero 5xx) and gate-tested (**23 files / 99 tests** on embedded PG, plus engine-core 294 and ci-shape 63 in the blocking merge gate; core/engine/cli/dashboard typecheck clean). | Area | Surfaces | State | |---|---|---| | Satellite stores | workflows, todos, insights, research, missions, goals, mailbox | ✅ | | Views | artifacts, documents, evals | ✅ | | Command Center | activity, productivity, team, tokens, tools, **workflows**, **github**, **signals**, **plugin-activations**, **live** (all 10) | ✅ | | Run execution | insight generation, research run execution | ✅ (store-path; AI step needs a provider) | | Live updates | SSE push for mission/research/insight events | ✅ | | Workflow editing | create / update / delete / select (+ id counter) | ✅ | | Engine | mission autopilot, incident-signal ingestion, regression storm-guard, agent wake-on-message | ✅ | | Core | tasks, agents, secrets, automations, memory, chat, usage, PRs, git | ✅ | ## Approach Each satellite store gets an `Async<Store>` wrapper exposing the sync store's method names over the existing `async-*-store.ts` helpers; `get<Store>Store()` returns a `Sync | Async` union; consumers `await` (harmless on sync), and engine/CLI paths that can't convert use `instanceof Sync` graceful fallback. Analytics aggregators branch on `"ping" in dbOrLayer` to run schema-qualified raw SQL over `project.*` (snake_case) in PG. Executors/orchestrators/autopilot are await-converted to drive the union store; the async store wrappers extend `EventEmitter` so SSE live-push fires in both backends. Not-yet-ported capabilities degrade gracefully (never 500) and are individually called out in commits. ## Sync with main The branch is kept continuously merged with `main` (currently through FN-7845, 2026-07-12); the earlier "final rebase deferred" note no longer applies. Use **Create a merge commit** (or squash) to land it — GitHub's rebase-merge cannot replay a merge-maintained branch. ## Residual Review Findings Multi-agent code review of the PostgreSQL satellite-store ports (U1–U5) applied 3 safe fixes (see `fix(review): apply autofix feedback`). The following are **real but gated** — recorded here as follow-up work rather than auto-applied. All are SQLite→PostgreSQL **concurrency/atomicity regressions**: the sync stores were immune only by SQLite's single-writer, single-threaded-handler execution; the async ports open multi-await read-modify-write windows. **Reachability is low today** because the execution engines that generate concurrent same-run mutations (insight run executor, research orchestrator/dispatcher) are `instanceof`-gated to sync mode in PG. No process-crash class survived (all engine fallbacks correctly guard the sync store). - **[P1] Research `appendResearchEvent` dual-write is non-atomic** (`packages/core/src/async-research-store.ts`, corroborated: adversarial + reliability). The `research_run_events` insert (own transaction) and the `run.events` jsonb update are separate writes — a crash between them, or two concurrent appends, splits the table count from the jsonb array. **Fix:** perform the seq-insert and the jsonb update in one `layer.transactionImmediate`. - **[P1] Research run terminal-reversion via stale full-row persist** (`async-research-store.ts` `persistResearchRun`/`updateResearchStatus`). Concurrent `PATCH /runs/:id/status` + `POST /runs/:id/events` can revert a terminal run to `running` by overwriting the whole row, bypassing the transition guard. **Fix:** scoped column `UPDATE`s with a `WHERE status …` guard, or optimistic version column. - **[P2] `updateResearchRun`/`updateInsightRun` read-then-write TOCTOU** — concurrent PATCHes last-writer-wins on the lifecycle merge. **Fix:** `SELECT … FOR UPDATE` / enclosing transaction. - **[P2] `upsertRun`/`createRunOrThrowConflict` check-then-create race** (`async-insight-store.ts`) — two callers can each create an "active" run. **Fix:** partial unique index on `(projectId, trigger) WHERE status IN ('pending','running')`. - **[P3] `createResearchRetryRun` return-value divergence** — sync returns the pre-update `queued` snapshot; async returns the reloaded `retry_waiting` run (persisted state is identical). Pick one side for cross-backend parity. - **[P2/perf] Mission `getMissionWithHierarchy`/`getMissionHealth` N+1 fan-out** — O(milestones×slices) sequential round-trips hold one pool slot per request; can starve the pool for large hierarchies. **Fix:** batched/joined reads. - **Testing gaps:** no PG-mode concurrency tests (interleaved status/event mutations), no sync↔async parity assertion for the lifecycle-error codes, and no mission status/health rollup parity test vs the sync `MissionStore`. ~~Out of scope (deferred): AI run *execution* (insight/research) + mission autopilot + live SSE mission events remain sync-gated/degraded in PG mode.~~ **Since ported** — insight/research run execution, mission autopilot, and SSE live push all run on the async layer now, which also makes the concurrency findings above genuinely reachable; they remain open follow-ups. --- ## Update — 2026-07-12: production-readiness hardening & live acceptance Everything below landed on this branch since the description above was written: **Production blockers from review — fixed** - `recoverStaleTransitionPending` ported to the async layer (backend moves write + clear the crash-safe marker; startup/maintenance sweeps no longer throw). - Lost-update class fixed: `atomicWriteTaskJson`/`WithAudit` write changed columns only (full-row upserts silently resurrected stale fields across concurrent store instances — the "task stuck unplanned forever" bug). - First-boot **auto-migration**: booting the PG backend over a project with a legacy `fusion.db` migrates it automatically (loud failure, SQLite kept as backup), and the dashboard shows a one-time **"your data was migrated" banner** with the backup paths and a Need-help Discord link. - `pg_dump`/`pg_restore` discovered from common install locations for embedded-mode backups. - The PG suite is part of the blocking merge gate (`test:pg-gate`). **Multi-project isolation (PR #2007, merged into this branch)** - `project_id` partition key on tasks / archived tasks / config, `taskProjectScope` threaded through every scan/claim/count, per-project config rows, layer bound to the project at startup. - Review P1 follow-up: the shared cold-storage `archive.archived_tasks` table is also partitioned and all archived-board reads/counts/searches are scoped. - Schema drift self-heal generalized to schema-qualified columns so existing databases upgrade in place. **Other changes** - Node settings sync **removed** in PG mode (409 `settings-sync-disabled-postgres`) — nodes share state by connecting to the same database; auth sync kept (per-machine file). - Perf (review findings): `listTasks` pushes column filter + ORDER BY + LIMIT/OFFSET into SQL; `getConversation` capped to the most recent 200 messages. - Fixed a false "operator action required" pause-abort log fired on every successfully auto-merged task. **Live acceptance — PASSED (2026-07-12)** A sandboxed instance (isolated HOME, embedded PG, real Opus executor) ran a task through the complete cycle: create → triage (AI spec) → execute → in-review → AI squash-merge landed on the project's `main` → done. A write+read sweep of every data surface (settings, comments, documents, attachments + artifact bridge + artifact edit, chat with real generation, goals, missions, agent mail, secrets, workflows, memory, CC analytics) was green on embedded PG. **Known remaining work** - The per-project `config` PK re-key has no upgrade path for pre-isolation embedded-PG databases (needs a real `DROP CONSTRAINT`/re-key migration; fresh databases are fine). - `pg_dump`/`pg_restore` binaries are not yet bundled in release artifacts (PATH/common-location discovery only). - The satellite-store concurrency findings listed above. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Phil Larson <hello@phillarson.xyz> Co-authored-by: fusion-merge <fusion-merge@local> |
||
|
|
1ff83a2735 |
chore(release): v0.60.0
Version bump via changesets. |
||
|
|
7246df22f6 |
FN-7944: add setting to keep task popups attached to their Board/List view
Adds an opt-in project setting so open task-detail popups stay attached to the Board or List view where they were opened, instead of floating over every main-content view. - New project setting taskPopupsBoardListOnly (default: off) in settings-schema.ts and ProjectSettings type, with default preserved via settings-defaults tests. - usePoppedOutTasks now stores each popup's originating TaskView alongside its task snapshot (PoppedOutTaskEntry), keeping legacy tasks output for existing callers. - App.tsx adds isTaskPopupVisibleForView() gating helper and filters popped-out entries to the current view for rendering/keyboard-close handling, while hidden popups remain mounted in hook state (not cleared) so switching back to the originating view restores them with shared persisted geometry. - Settings -> Appearance gets a new "Keep task popups on their Board/List view" checkbox (AppearanceSection.tsx) with i18n strings and updated settings search text in SettingsModal. - Documentation updated in docs/dashboard-guide.md and docs/settings-reference.md to describe the render-only hide/restore behavior. - New/updated tests: App.taskPopupViewGating.test.tsx, usePoppedOutTasks.test.ts, AppearanceSection.test.tsx, settings-default-descriptions.test.tsx, settings-defaults.test.ts. Files changed: docs/dashboard-guide.md | 5 +- docs/settings-reference.md | 1 + .../core/src/__tests__/settings-defaults.test.ts | 13 +++ packages/core/src/settings-schema.ts | 5 + packages/core/src/types.ts | 7 ++ packages/dashboard/app/App.tsx | 49 +++++++-- .../app/__tests__/App.taskPopupViewGating.test.tsx | 113 +++++++++++++++++++++ .../dashboard/app/components/SettingsModal.tsx | 3 +- .../settings/sections/AppearanceSection.tsx | 8 ++ .../sections/__tests__/AppearanceSection.test.tsx | 21 ++++ .../settings-default-descriptions.test.tsx | 1 + .../app/hooks/__tests__/usePoppedOutTasks.test.ts | 14 +++ packages/dashboard/app/hooks/useAppSettings.ts | 4 + packages/dashboard/app/hooks/usePoppedOutTasks.ts | 27 +++-- packages/i18n/locales/en/app.json | 2 + 15 files changed, 255 insertions(+), 18 deletions(-) Fusion-Task-Id: FN-7944 Fusion-Task-Lineage: 4b8ced0e-1853-429f-8482-163821a35ae6 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
d4001ab0ee |
feat: make merger AI model configurable under Global and Project Models
Add a dedicated merger model lane (project + global provider/model/thinking) so merge-agent sessions no longer share only the default model, without inheriting executor/planner/reviewer lanes. |
||
|
|
502c4c132f |
chore(release): v0.59.0
Version bump via changesets. |
||
|
|
9ba8a2e575 |
FN-7932: add per-lane Reviewer and Planning thinking-level overrides
Adds validatorThinkingLevel and planningThinkingLevel task fields so the Reviewer and Planning AI lanes can override reasoning effort independently of the shared task thinkingLevel, with dashboard UI, storage, and runtime fallback wiring. - Add validatorThinkingLevel and planningThinkingLevel to Task/TaskCreateInput types (packages/core/src/types.ts) - Persist the new fields in the SQLite schema and store read/write/replication paths (packages/core/src/db.ts, store.ts, mesh-task-replication.ts) - Wire executor and triage lanes to fall back per-lane thinking level -> task.thinkingLevel -> existing settings/lane fallback (packages/engine/src/executor.ts, triage.ts) - Add per-lane thinking-level selectors to the ModelSelectorTab UI, alongside the existing thinking-level control (packages/dashboard/app/components/ModelSelectorTab.tsx) - Expose the new fields through the legacy task API and task-workflow routes (packages/dashboard/app/api/legacy.ts, packages/dashboard/src/routes/register-task-workflow-routes.ts) - Document the new settings in dashboard-guide.md and settings-reference.md - Add a minor changeset and unit/integration test coverage for store persistence, routes, UI, and agent-session helpers Files changed: .changeset/per-lane-task-thinking.md | 7 ++ docs/dashboard-guide.md | 2 + docs/settings-reference.md | 2 +- .../src/__tests__/store-thinking-levels.test.ts | 43 +++++++ packages/core/src/db.ts | 15 ++- packages/core/src/mesh-task-replication.ts | 4 + packages/core/src/store.ts | 24 +++- packages/core/src/types.ts | 12 ++ packages/dashboard/app/api/legacy.ts | 2 + .../dashboard/app/components/ModelSelectorTab.tsx | 126 ++++++++++++++++++++- .../components/__tests__/ModelSelectorTab.test.tsx | 50 +++++++- .../src/__tests__/routes-tasks-ops.test.ts | 74 ++++++++++++ .../src/routes/register-task-workflow-routes.ts | 19 +++- .../src/__tests__/agent-session-helpers.test.ts | 15 +++ packages/engine/src/executor.ts | 16 ++- packages/engine/src/triage.ts | 8 +- 16 files changed, 395 insertions(+), 24 deletions(-) Fusion-Task-Id: FN-7932 Fusion-Task-Lineage: 4202f774-aab9-41d2-86a0-f5277dd0f848 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
daf3f15fd1 |
FN-7909: add room-level thinking effort override for Chat Rooms
Adds a per-room thinking-effort (reasoning level) override for Chat Rooms so all room responders can share a consistent override instead of relying only on per-agent/global defaults. - Persist `chat_rooms.thinkingLevel` with a new core DB migration and store read/write support - Extend chat-store and chat-types with thinkingLevel plumbing for room create/update - Wire the dashboard chat room API/routes and legacy handlers to accept and return thinkingLevel - Add a ChatView room settings control (with CSS) and useChatRooms hook support for setting/clearing the override - Resolve room responder defaultThinkingLevel from the room override when present - Update docs (dashboard-guide, settings-reference) and add a minor changeset for the feature Files changed: .changeset/fn-7909-room-thinking-level.md | 7 +++ docs/dashboard-guide.md | 1 + docs/settings-reference.md | 2 +- packages/core/src/__tests__/chat-store.test.ts | 21 ++++++++ packages/core/src/__tests__/db-migrate.test.ts | 57 ++++++++++++++++++++++ packages/core/src/chat-store.ts | 12 ++++- packages/core/src/chat-types.ts | 10 ++++ packages/core/src/db.ts | 28 ++++++++++- packages/dashboard/app/api/__tests__/chat-rooms-api.test.ts | 8 +-- packages/dashboard/app/api/legacy.ts | 4 +- packages/dashboard/app/components/ChatView.css | 16 ++++++ packages/dashboard/app/components/ChatView.tsx | 29 ++++++++++- packages/dashboard/app/components/__tests__/ChatView.rooms.test.tsx | 24 +++++++++ packages/dashboard/app/hooks/__tests__/useChatRooms.test.ts | 22 +++++++++ packages/dashboard/app/hooks/useChatRooms.ts | 16 ++++++ packages/dashboard/src/__tests__/chat-room-routes.test.ts | 26 ++++++++++ packages/dashboard/src/__tests__/chat.rooms.test.ts | 42 ++++++++++++++++ packages/dashboard/src/chat.ts | 8 +++ packages/dashboard/src/routes/register-chat-room-routes.ts | 21 ++++++-- 19 files changed, 338 insertions(+), 16 deletions(-) Fusion-Task-Id: FN-7909 Fusion-Task-Lineage: 2741eca9-5305-4f6c-81bf-ae644a9fe307 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
a628a13888 |
feat: stamp store-open provenance into run-audit
Every TaskStore.init() now records a `store:open` run-audit event with pid/ppid/execPath/entry/cwd/node version. Motivated by the FN-7910 incident: a stale pre-fix binary opened the shared fusion.db and evacuated Ideas cards, and the audit trail (agentId:"system", no PID) could not identify the writer. Any future mystery mutation is now attributable to the process that opened the store. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8835c6cb48 |
FN-7908: add in-chat model/agent switcher to brain-icon popup
Extend the chat brain-icon popup and its backing session PATCH route so an active Direct chat's model or agent can be switched mid-conversation instead of only being set at creation time. - Add a Model/Agent section to ChatThinkingLevelControl (the brain-icon popup) for picking a model provider/model or retargeting to a real agent without leaving the chat. - Extend PATCH /api/chat/sessions/:id to accept modelProvider/modelId (as a validated pair via the existing validateModelPair helper) and agentId, forwarding only the keys present in the body so omitted fields leave the session's stored target untouched. - Add chat-store updateSession support for the agentId clause alongside the existing model/thinkingLevel fields, and a useChat.setSessionModel hook for the dashboard to call the new PATCH capability. - Update i18n locale strings (en/es/fr/ko/zh-CN/zh-TW) and dashboard-guide.md docs for the new switcher UI. - Add unit/integration test coverage across chat-store, chat-manager, chat-routes, useChat, ChatThinkingLevelControl, and ChatView for the new model/agent switch behavior. - Add changeset fn-7908-chat-model-agent-switcher.md (minor, @runfusion/fusion). Files changed: .changeset/fn-7908-chat-model-agent-switcher.md | 7 + docs/dashboard-guide.md | 3 +- packages/core/src/__tests__/chat-store.test.ts | 21 ++ packages/core/src/chat-store.ts | 8 + packages/core/src/chat-types.ts | 2 + packages/dashboard/app/api/legacy.ts | 11 +- .../app/components/ChatThinkingLevelControl.tsx | 219 ++++++++++++++++++--- packages/dashboard/app/components/ChatView.css | 135 ++++++++++++- packages/dashboard/app/components/ChatView.tsx | 23 ++- .../__tests__/ChatThinkingLevelControl.test.tsx | 109 +++++++++- .../__tests__/ChatView.thinking-level.test.tsx | 67 ++++++- .../dashboard/app/hooks/__tests__/useChat.test.ts | 166 +++++++++++++++- packages/dashboard/app/hooks/useChat.ts | 56 ++++++ .../dashboard/src/__tests__/chat-manager.test.ts | 38 ++++ .../dashboard/src/__tests__/chat-routes.test.ts | 117 ++++++++++- .../dashboard/src/routes/register-chat-routes.ts | 48 ++++- packages/i18n/locales/en/app.json | 8 +- packages/i18n/locales/es/app.json | 8 +- packages/i18n/locales/fr/app.json | 8 +- packages/i18n/locales/ko/app.json | 8 +- packages/i18n/locales/zh-CN/app.json | 8 +- packages/i18n/locales/zh-TW/app.json | 8 +- 22 files changed, 1007 insertions(+), 71 deletions(-) Fusion-Task-Id: FN-7908 Fusion-Task-Lineage: b1104865-9b0c-4d77-973e-89152fe245e0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
03bca1787b |
FN-7907: Add project chat default (model/agent) setting and New Chat default behavior
Adds a project-scoped Direct-chat default (model pair or durable agent) plus a New Chat behavior toggle (prompt vs. always-use-default), surfaced in Project Models settings using the standard model dropdown. - Add ProjectSettings fields: chatNewSessionMode, chatDefaultKind, chatDefaultAgentId, chatDefaultModelProvider, chatDefaultModelId, chatDefaultThinkingLevel - Extend settings-schema validation for the new chat default fields - Add a "Chat" subsection to ProjectModelsSection with New Chat behavior selector, model/agent target toggle, and the standard CustomModelDropdown for model selection - Update ChatView.tsx handleNewChat() flow to honor the configured default (prompt vs. immediate session creation) - Add i18n strings across en/es/fr/ko/zh-CN/zh-TW locales and regenerate resources.d.ts - Add changeset (@runfusion/fusion minor) and update docs/settings-reference.md and docs/dashboard-guide.md - Add settings-parity, ChatView new-chat-default, and ProjectModelsSection chatDefault test coverage Files changed: .changeset/fn-7907-chat-default.md | 7 + docs/dashboard-guide.md | 3 +- docs/settings-reference.md | 8 + packages/core/src/__tests__/settings-parity.test.ts | 20 ++ packages/core/src/settings-schema.ts | 6 + packages/core/src/types.ts | 15 + packages/dashboard/app/components/ChatView.tsx | 93 +++++- packages/dashboard/app/components/__tests__/ChatView.new-chat-default.test.tsx | 357 +++++++++++++++++++++ packages/dashboard/app/components/settings/sections/ProjectModelsSection.tsx | 77 +++++ packages/dashboard/app/components/settings/sections/__tests__/ProjectModelsSection.chatDefault.test.tsx | 193 +++++++++++ packages/i18n/locales/en/app.json | 20 +- packages/i18n/locales/es/app.json | 20 +- packages/i18n/locales/fr/app.json | 20 +- packages/i18n/locales/ko/app.json | 20 +- packages/i18n/locales/zh-CN/app.json | 20 +- packages/i18n/locales/zh-TW/app.json | 20 +- packages/i18n/src/resources.d.ts | 18 ++ 17 files changed, 899 insertions(+), 18 deletions(-) Fusion-Task-Id: FN-7907 Fusion-Task-Lineage: 86a81b9c-7e0c-4033-9e2a-6f7380810fe9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
d4bbbcccc6 |
fix: stop Ideas-intake cards from auto-processing and keep replans in the workflow's own planner column
Root cause of the reported incident: store init ran the retired flag-off evacuation on every open, dumping Coding (Ideas) intake cards into triage where they were auto-planned and executed. Init now always runs the workflow-aware integrity pass (with a stale-selection mis-mapping guard and per-pass IR memoization) and evacuation remains toggle-only. Engine rebounds (Plan Review REVISE, stale-spec, fs-validation) resolve a workflow-aware replan column instead of hardcoding triage; needs-replan now counts as unplanned for hold-release dispatch so rejected plans cannot re-execute; triage rediscovers needs-replan todo cards and refinement seed prompts (shared buildRefinementSeedPrompt/isUnplannedSeedPrompt); the fs-validation rebound sets needs-replan so unreadable-prompt tasks re-spec instead of livelocking. Dashboard: the All-workflows board renders column-orphaned tasks instead of silently dropping them (hidden columns stay hidden), and the FN-7591 refetch also fires for present-but-unrepresentable workflow mappings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
635d78248b |
FN-7900: persist thinkingLevel override for schedule and routine AI steps
Adds a persisted, optional per-step reasoning-effort (thinkingLevel) override for AI-capable schedule and routine automation steps, surfaced in the editors and validated at the route layer. - Add optional AutomationStep.thinkingLevel field (packages/core/src/automation.ts), riding the existing JSON steps blob so no DB migration is needed; runtime application of the level is deferred to a follow-up. - Validate thinkingLevel in dashboard route step validation against the shared THINKING_LEVELS set, rejecting unknown values (packages/dashboard/src/routes.ts). - Add Thinking Level controls to RoutineEditor, ScheduleForm, and ScheduleStepsEditor so users can set/inherit the override per step. - Extend core and dashboard test suites (automation-store, routine-store, RoutineEditor, ScheduleForm, ScheduleStepsEditor, routes-automation) to cover persistence, validation, and UI behavior. - Update dashboard-guide.md docs and add a minor changeset for the new feature. Files changed: .changeset/fn-7900-automation-thinking-level.md | 7 + docs/dashboard-guide.md | 3 +- .../core/src/__tests__/automation-store.test.ts | 45 ++++++ packages/core/src/__tests__/routine-store.test.ts | 46 +++++++ packages/core/src/automation.ts | 9 ++ .../dashboard/app/components/RoutineEditor.tsx | 21 ++- packages/dashboard/app/components/ScheduleForm.tsx | 28 +++- .../app/components/ScheduleStepsEditor.tsx | 21 ++- .../components/__tests__/RoutineEditor.test.tsx | 99 +++++++++++++- .../app/components/__tests__/ScheduleForm.test.tsx | 137 +++++++++++++++++-- .../__tests__/ScheduleStepsEditor.test.tsx | 83 +++++++++-- .../src/__tests__/routes-automation.test.ts | 152 +++++++++++++++++++++ packages/dashboard/src/routes.ts | 10 ++ 13 files changed, 622 insertions(+), 39 deletions(-) Fusion-Task-Id: FN-7900 Fusion-Task-Lineage: 812a9a8c-ad0f-462f-b1c6-9900f70e4261 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
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> |
||
|
|
9cfb40e137 |
FN-7863: add bounded execute-node self-requeue loop guard
Bounds the execute->pause-abort->todo dispatch loop so a task can no longer requeue forever with no visible signal or terminal state. - Track a progress-anchored `executeRequeueLoopCount`/`executeRequeueLoopSignature` pair on the task row (current step + step statuses) so slow no-progress requeue cycles are counted independently of the scheduler's wall-clock `dispatchStormCount` guard. - Warn visibly in the task log at `EXECUTE_REQUEUE_LOOP_VISIBLE_THRESHOLD` (3) and terminalize non-paused, non-terminal tasks at `MAX_EXECUTE_REQUEUE_LOOP_CYCLES` (6) with `status:"failed"` and an `EXECUTION_DISPATCH_LOOP_EXHAUSTED:` error, preserving worktree/branch/step progress. - Emit a new `task:execution-dispatch-loop-terminalized` run-audit mutation type with ids/counts/outcomes-only metadata. - Reset the loop counters on real progress, manual retry, forward moves (in-review/done/archived), and unpause, in both the executor and scheduler. - Add DB migration 142 (`executeRequeueLoopCount`, `executeRequeueLoopSignature` columns) plus store read/write/reset plumbing. - Add reliability-interactions coverage for the new loop guard and extend store-persistence tests for the new columns. - Document the new behavior in AGENTS.md and docs/architecture.md. Files changed: AGENTS.md | 1 + docs/architecture.md | 2 + packages/core/src/__tests__/store-persistence.test.ts | 45 +++++ packages/core/src/db.ts | 17 +- packages/core/src/manual-retry-reset.ts | 1 + packages/core/src/store.ts | 22 ++- packages/core/src/types.ts | 11 ++ .../execute-requeue-loop-guard.test.ts | 188 +++++++++++++++ packages/engine/src/executor.ts | 67 +++++++- packages/engine/src/run-audit.ts | 2 + packages/engine/src/scheduler.ts | 8 +- 11 files changed, 355 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-7863 Fusion-Task-Lineage: db40507f-5851-435e-8854-c1ed695b4154 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
0c97c161ee |
FN-7860: honor plugin skillFiles paths for skill body resolution
Plugin skills declared with PluginSkillContribution.skillFiles were silently ignored by the host, forcing plugin authors into a flat skills/<name>/SKILL.md layout instead of category subdirectories. - Add packages/core/src/plugin-skill-paths.ts with resolvePluginSkillBodyPath (honors skillFiles[0] relative to plugin root, falls back to skills/<name>/SKILL.md, rejects path traversal) and resolvePluginRootFromEntryPath - Track per-plugin absolute roots in PluginLoader and expose pluginRoot alongside each getPluginSkills() contribution - Thread pluginRoot/skillFiles through PluginRunner, dashboard server/chat structural types, and skills-adapter so discovered plugin skill path/relativePath resolve via the new traversal-guarded resolver when a pluginRoot is available, keeping the old name-derived path for backward compatibility otherwise - Export resolvePluginSkillBodyPath/resolvePluginRootFromEntryPath/PluginSkillBodyPath from @fusion/core - Update docs/PLUGIN_AUTHORING.md and add unit tests covering the new resolver and updated plugin-loader/skills-adapter/plugin-runner behavior - Add changeset (@runfusion/fusion: minor, category: fix) Files changed: .changeset/fn-7860-plugin-skillfiles.md | 7 ++ docs/PLUGIN_AUTHORING.md | 4 +- packages/core/src/__tests__/plugin-loader.test.ts | 23 +++++++ .../core/src/__tests__/plugin-skill-paths.test.ts | 75 ++++++++++++++++++++++ packages/core/src/index.ts | 5 ++ packages/core/src/plugin-loader.ts | 20 +++++- packages/core/src/plugin-skill-paths.ts | 58 +++++++++++++++++ .../dashboard/src/__tests__/skills-adapter.test.ts | 75 +++++++++++++++++++++- packages/dashboard/src/chat.ts | 2 +- packages/dashboard/src/server.ts | 2 +- packages/dashboard/src/skills-adapter.ts | 19 ++++-- .../engine/src/__tests__/plugin-runner.test.ts | 2 +- packages/engine/src/plugin-runner.ts | 4 +- 13 files changed, 280 insertions(+), 16 deletions(-) Fusion-Task-Id: FN-7860 Fusion-Task-Lineage: 720cf527-9c6f-4877-838e-5fb64bd86556 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
c13d2ee9c2 |
FN-7858: honor per-project plugin-skill toggles in session merging
Session skill merging (collectPluginSkillNames) previously ignored per-project Skills view enable/disable toggles and only consulted each plugin's static default, so a user disabling a plugin skill in the Skills view would still see it merged into live agent sessions. Extracted the effective-enablement resolver shared by dashboard discovery and engine session assembly into @fusion/core so both surfaces stay in sync. - Added packages/core/src/skill-settings.ts with computeSkillId/parseSkillId/ normalizeStoredSkillPath/getSkillSettingState/resolvePluginSkillEnabled, exported from @fusion/core's index. - packages/dashboard/src/skills-adapter.ts now re-exports and delegates to the shared @fusion/core resolver instead of duplicating its own getSkillSettingState/computeSkillId/parseSkillId implementations. - packages/engine/src/session-skill-context.ts: collectPluginSkillNames now accepts a projectRootDir, reads project settings via skill-resolver's newly exported readProjectSettings/resolveProjectRoot, and calls resolvePluginSkillEnabled instead of only checking the plugin's static skill.enabled flag; mergePluginSkills passes projectRootDir through. - packages/engine/src/skill-resolver.ts: exported readProjectSettings and ProjectSkillSettings for reuse by session-skill-context. - Updated docs/plugin-management.md to document that per-project Skills view toggles now apply to runtime agent sessions, not just discovery. - Added unit tests for the new core resolver and updated dashboard/engine tests to cover per-project toggle overrides in session merging. - Added a patch changeset for @runfusion/fusion. Files changed: .changeset/fn-7858-plugin-skill-session-toggle.md | 7 ++ docs/plugin-management.md | 4 +- packages/core/src/__tests__/skill-settings.test.ts | 62 +++++++++ packages/core/src/index.ts | 8 ++ packages/core/src/skill-settings.ts | 102 +++++++++++++++ .../dashboard/src/__tests__/skills-adapter.test.ts | 60 ++++++++- packages/dashboard/src/skills-adapter.ts | 107 +++------------- .../src/__tests__/session-skill-context.test.ts | 140 ++++++++++++++++++++- packages/engine/src/session-skill-context.ts | 23 +++- packages/engine/src/skill-resolver.ts | 4 +- 10 files changed, 409 insertions(+), 108 deletions(-) Fusion-Task-Id: FN-7858 Fusion-Task-Lineage: 90e44d24-e385-4a74-b8e4-3c864ec39a95 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
729298fa36 |
FN-7855: refresh persisted plugin manifest metadata on reload/re-import
Path-registered plugin reload/restart now refreshes persisted manifest metadata instead of leaving stale version/settingsSchema in the store. - PluginLoader.loadPlugin/reloadPlugin call a new refreshPersistedManifestMetadata helper after each fresh module import, generalizing the previously bundled-only refresh to path-registered plugins - Refresh is metadata-only (version, settingsSchema) via a stable-JSON comparison, preserving per-project enablement and saved setting values, and is a no-op when nothing changed - PluginStore.PluginUpdateInput/updatePlugin gain a settingsSchema field (undefined = unchanged, null = explicitly clear) so updatePlugin can persist manifest schema changes independently of setting values - Docs: add a "Updating path-registered plugins" section to docs/PLUGIN_AUTHORING.md describing the new reload/refresh loop - Tests: add coverage in plugin-loader.test.ts and plugin-store.test.ts for manifest metadata refresh on load/reload and settingsSchema persistence - Add a patch changeset for @runfusion/fusion Files changed: .changeset/fn-7855-plugin-manifest-refresh.md | 7 + docs/PLUGIN_AUTHORING.md | 10 ++ packages/core/src/__tests__/plugin-loader.test.ts | 177 ++++++++++++++++++++++ packages/core/src/__tests__/plugin-store.test.ts | 42 +++++ packages/core/src/plugin-loader.ts | 53 +++++++ packages/core/src/plugin-store.ts | 10 ++ 6 files changed, 299 insertions(+) Fusion-Task-Id: FN-7855 Fusion-Task-Lineage: f4d94023-5a27-4059-a7a5-61f524c171b8 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
8b601810e0 |
fix(FN-7851): enforce per-agent assignment policy across all task-routing binding primitives
Issue #2015: product-code executor tasks were repeatedly routed to a liaison-only agent because every routing path gated only on the coarse role field, and several binding primitives had no guard at all. - Add runtimeConfig.assignmentPolicy ("auto" | "explicit-only" | "none"); "none" can never be bound to implementation tasks by ANY path — no override bypasses it (the liaison guarantee) - Route every binding surface through one shared evaluator (evaluateImplementationTaskBind): claimTaskForAgent, the previously unguarded checkoutTask/assignTask primitives, selectNextTaskForAgent (including the in-progress re-selection loop), scheduler auto-assign pool, heartbeat inbox/auto-claim, fn_delegate_task, CLI agent-id validation, and dashboard assign/checkout/inbox routes - Lock project isolation with a regression test: a foreign-project agent id is rejected by every binding primitive - Expose Assignment Policy in Agent Detail settings; document in docs/agents.md; add changeset Fusion-Task-Id: FN-7851 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f23619c2d4 |
fix: preserve user pause across executor pause teardown (FN-7851 pause-bounce loop)
Pausing an in-progress task never stuck: the pause teardown re-queued the row to todo with a plain engine move, and the reopen block wiped paused/pausedByAgentId/pausedReason. The graph-failure classifier then saw an unpaused row, misread the hard-cancel as an engine-internal abort, and auto-continued the session (graphResumeRetryCount 1/2, 2/2); once the budget was exhausted the benign re-queue left the row dispatchable and the scheduler re-dispatched it seconds later — an indefinite pause/resume bounce, burning a fresh worktree + pnpm install per cycle. - store: new moveTask option `preservePause` keeps the pause park across a reopen-to-todo/triage move (flag-ON trait hook + flag-OFF legacy inline, kept in sync). It never SETS a pause, only prevents clearing one. - executor teardown: when the pause that caused the abort is still in force, move with preservePause so the row lands in todo still parked (scheduler skips paused/userPaused rows until explicit unpause). - classifier: a live task pause is labeled operator intent, never "engine abort during pause/resume"; the benign log now says "parked … awaiting explicit unpause" instead of the contradictory "cleared for normal scheduling" for parked rows. Surfaces covered by tests: flag-ON hook (preserve + never-set + default clear), classifier no-auto-continue for task-pause/user-pause/global-pause rows in todo, provenance labels. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f82a3d2840 |
chore(release): v0.58.0
Version bump via changesets. |
||
|
|
d99c04cded |
FN-7847: add pricing for GLM-5.2, MiniMax-M3, and Kimi K2.6
Adds static MODEL_PRICING rows for three previously-unpriced models so their token usage renders a dollar cost instead of "—" in the dashboard. - Add zai:glm-5.2, minimax:minimax-m3, and kimi-coding:kimi-k2.6-preview pricing rows to MODEL_PRICING, sourced from each provider's public pricing docs - Bump pricingAsOf to 2026-07-11 - Add regression tests asserting costFor() prices these three models (not unavailable) and that lookupPricing() resolves them by provider-normalized and bare model-id keys - Add a minor changeset documenting the pricing addition for @runfusion/fusion release notes Files changed: .changeset/fn-7847-model-pricing.md | 7 +++++ packages/core/src/__tests__/model-pricing.test.ts | 24 +++++++++++++++ packages/core/src/model-pricing.ts | 36 ++++++++++++++++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-7847 Fusion-Task-Lineage: 6c138aa3-53e9-4165-909d-c8fc02acb48b Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |