## Summary
Fixes all remaining full-suite failures from run 29231108919
(FN-7923–7931 drift) + expands the structural mock-completeness guard to
cover dashboard tests.
## Fixes
### Dashboard (shard 4) — 4 files
- **`TaskCard.cli-states.test.tsx`** — Proxy lucide-react mock needed
`has`/`getOwnPropertyDescriptor` traps; TaskCard now imports
`priorityIndicator.tsx` which reads `ArrowDown` at module-init. Vitest
validates ESM named exports via `in`/descriptor, not `get`. Also added
`useToast` mock.
- **`SettingsModalNodeRouting.test.tsx`** — Pass
`initialSection="node-routing"` (it's in
`ADVANCED_SETTINGS_SECTION_IDS`, nav hidden by default).
- **`styles-css-rgba-tokenization.test.ts`** — Removed stale
`.settings-sidebar` color-mix expectation (FN-7825 made it
structural-only).
- **`mcp-helper-forwarding.test.ts`** — Added
`resolvePlanningThinkingLevel` to `@fusion/engine` mock (insight
extraction calls it before MCP forwarding).
### Structural guard expansion
- **`.tsx` blind spot fixed** — `collectTs` and test file filter now
include `.tsx` files
- **Shorthand property extraction** — key extractor now matches both
`key: value` and `key,` (shorthand)
- **Convention mapping** — `.test.tsx → .tsx` source resolution added
- **Dashboard test coverage** — `@fusion/engine` barrel check now scans
`packages/dashboard/src/__tests__/`
- **7 latent mock gaps completed** — `pr-conflict-resolver`,
`project-pause-resume-routes`, `routes-approval-sandbox-provisioning`,
`routes-approval`, `routes-worktrunk`, `session-reconnect`,
`setup-routes`
### Engine (shards 1+2) — zero real failures
All 3 failing files are local-only (`@agentclientprotocol/sdk` + pi-ai
staleness). CI resolves them from lockfile.
## Verification
- Gate (with expanded guard): exit 0 ✅
- Dashboard (5 non-local files): 36/36 passed ✅
- 3 files skipped locally (`@agentclientprotocol/sdk`) — CI will verify
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Tests**
* Improved test reliability by completing module mocks across dashboard
and routing scenarios.
* Updated settings and task card test coverage to reflect current UI
behavior.
* Enhanced mock validation to cover additional test files, TypeScript
React files, and shorthand exports.
* Prevented failures related to missing providers, engine helpers, and
planning configuration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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>
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>
Threads an optional per-session reasoning-effort (thinkingLevel) through mission planning and planning-mode agent sessions so the selected level survives draft reopen, session start, and agent rebuild.
- Add ThinkingLevel to DraftInputPayload/session state in planning.ts; persist and restore it in inputPayload alongside model overrides, threading it into createDraftSession, startExistingSession, createSessionWithAgent, initializeAgent, createPlanningAgent, and ensureSessionAgent as defaultThinkingLevel.
- Preserve thinkingLevel across draft syncs in ai-session-store.ts's updateDraft so it isn't erased when the model pair is unchanged.
- Extend mission-interview.ts session state/persistence and createMissionInterviewAgent/createMissionInterviewSession to accept and persist a validated thinkingLevel, defaulting the agent's reasoning effort from it.
- Validate thinkingLevel against THINKING_LEVELS in mission-routes.ts's POST /api/missions/interview/start and thread it through to the interview session.
- Update PlanningModeModal.tsx and MissionInterviewModal.tsx to surface and submit the selected thinking level; update legacy.ts API client to pass it through.
- Extend register-planning-subtask-routes.ts to accept/validate/forward thinkingLevel for subtask planning routes.
- Add regression coverage in mission-interview.test.ts, routes-planning.test.ts, and session-persistence-roundtrip.test.ts.
- Add changeset for @runfusion/fusion (minor): persisted thinking-level controls for Mission Interview and Planning mode.
Files changed:
.changeset/quiet-dragons-think.md | 7 ++
packages/dashboard/app/api/legacy.ts | 14 ++-
.../app/components/MissionInterviewModal.tsx | 21 +++-
.../dashboard/app/components/PlanningModeModal.tsx | 34 ++++--
.../src/__tests__/mission-interview.test.ts | 34 ++++++
.../src/__tests__/routes-planning.test.ts | 17 ++-
.../session-persistence-roundtrip.test.ts | 16 +++
packages/dashboard/src/ai-session-store.ts | 13 +-
packages/dashboard/src/mission-interview.ts | 30 ++++-
packages/dashboard/src/mission-routes.ts | 14 ++-
packages/dashboard/src/planning.ts | 70 ++++++++---
packages/dashboard/src/routes.ts | 11 +-
.../src/routes/register-planning-subtask-routes.ts | 135 +++++++++++++++------
13 files changed, 330 insertions(+), 86 deletions(-)
Fusion-Task-Id: FN-7902
Fusion-Task-Lineage: 751fb238-fd67-4870-975d-3b10dadde1a0
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The task cards already show Retry for needs-replan/planning/failed states, but
the retry route only offered the planning retry when the card sat in "triage",
so plan-in-place workflows (Coding (Ideas) replans in Todo) got a 400 "not in a
retryable state". The retrySpecification gate is now workflow-aware: a Todo card
whose workflow declares no "triage" column takes the planning-retry path;
default-workflow Todo cards keep the generic-retry semantics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Adds a persisted Thinking Level (reasoning-effort) selector to manual insight generation, threading the selection through the dashboard API, insight run metadata, and retries.
- Add inline Thinking Level selector to the InsightsView model-config popover, persisted to localStorage (fusion-insight-thinking)
- Thread thinkingLevel through triggerInsightRun (legacy API client) and useInsights.runInsights
- Validate and store thinkingLevel in insight run inputMetadata.metadata on the POST /insights/run route; resolve it via resolvePlanningThinkingLevel for the actual generation call
- Recover and reapply the original run's thinkingLevel on retry (retryInsightRunLifecycle) so retries reuse the same reasoning-effort setting
- Export resolvePlanningThinkingLevel from @fusion/engine
- Document the new Thinking Level selector in docs/dashboard-guide.md
- Add a minor changeset for @runfusion/fusion
Files changed:
.changeset/fn-7901-insight-thinking-level.md | 7 ++
docs/dashboard-guide.md | 1 +
.../app/__tests__/insight-model-selector.test.tsx | 41 ++++++++++-
packages/dashboard/app/api/legacy.ts | 2 +
packages/dashboard/app/components/InsightsView.tsx | 24 +++++-
.../app/hooks/__tests__/useInsights.test.ts | 36 ++++++++-
packages/dashboard/app/hooks/useInsights.ts | 6 +-
.../src/__tests__/insights-routes.test.ts | 86 ++++++++++++++++++++++
packages/dashboard/src/insights-routes.ts | 36 ++++++++-
packages/engine/src/index.ts | 1 +
10 files changed, 227 insertions(+), 13 deletions(-)
Fusion-Task-Id: FN-7901
Fusion-Task-Lineage: a6249526-e97d-403e-b853-e497d16f425b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Bring thinking-level (reasoning effort) editing to every remaining model selector surface that previously lacked it, so operators can set it consistently from Agent Detail, Agent Onboarding, and the List view's bulk task editor, in addition to the batch-update-models API and route that back them.
- Agent Detail config tab: persist/edit a built-in agent's runtimeConfig.thinkingLevel inline via the shared model dropdown, with dirty-state and reset tracking.
- Agent Onboarding modal: replace the read-only thinking-level input with an editable control wired into the same model dropdown used for creation.
- List view bulk edit toolbar: add a "no change" / "use default" / explicit-level thinking selector alongside executor/reviewer model and node overrides, wired through to the bulk apply action.
- Dashboard API client (`batchUpdateTaskModels`) and `/api/tasks/batch-update-models` route: accept and validate an optional `thinkingLevel` field (against `THINKING_LEVELS`), applying it per task alongside existing model/node updates.
- Update dashboard-guide.md docs and add regression tests across AgentDetailView, AgentOnboardingModal, ListView, and the batch-update-models route.
- Add a minor changeset documenting the feature for release notes.
Files changed:
.changeset/thinking-level-selector-parity.md | 7 ++
docs/dashboard-guide.md | 5 +-
packages/dashboard/app/api/legacy.ts | 3 +
.../dashboard/app/components/AgentDetailView.tsx | 20 +++++-
.../app/components/AgentOnboardingModal.tsx | 11 +++-
packages/dashboard/app/components/ListView.tsx | 54 +++++++++++++---
.../__tests__/AgentDetailView.settings.test.tsx | 45 +++++++++++++
.../__tests__/AgentDetailView.test-helpers.ts | 19 +++++-
.../__tests__/AgentOnboardingModal.test.tsx | 41 +++++++++++-
.../app/components/__tests__/ListView.test.tsx | 43 ++++++++++++-
.../src/__tests__/routes-tasks-ops.test.ts | 75 ++++++++++++++++++++++
.../src/routes/register-task-workflow-routes.ts | 22 +++++--
12 files changed, 323 insertions(+), 22 deletions(-)
Fusion-Task-Id: FN-7899
Fusion-Task-Lineage: fd584ce4-42b3-4c20-8de5-4d3c8963f593
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a best-effort, idempotent dashboard inbox notice announcing the upcoming embedded-Postgres storage migration, delivered once per project on the first engine start under the Fusion 0.59.x release line.
- New `deliverPostgresMigrationNoticeIfNeeded` in `@fusion/engine` (`postgres-migration-notice.ts`) builds and sends a `system` -> `user` inbox message via `MessageStore`, gated to version `0.59.x` by `isPostgresMigrationNoticeVersion`
- Idempotency via existing inbox message `metadata.kind = "postgres-migration-notice"` marker (no new settings key or table), so restarts never duplicate the notice
- Delivery is fully best-effort: any `MessageStore` failure is caught, logged as a warning, and never blocks or fails `ProjectEngine.start()`
- `ProjectEngine.start()` invokes the notice after runtime start, using an injected `cliPackageVersion` threaded from the CLI layer through `EngineManagerOptions` / `ProjectEngineOptions` so the engine never imports CLI/dashboard code directly
- `daemon.ts`, `dashboard.ts`, and `serve.ts` resolve the published `@runfusion/fusion` version via `getCliPackageVersion` / `isUnresolvedCliPackageVersion` and pass it into `ProjectEngineManager`
- Exported new symbols (`POSTGRES_MIGRATION_HELP_URL`, `POSTGRES_MIGRATION_NOTICE_KIND`, `deliverPostgresMigrationNoticeIfNeeded`, `isPostgresMigrationNoticeVersion`, related types) from `@fusion/engine`, and `isUnresolvedCliPackageVersion` from `@fusion/dashboard`
- New unit tests covering version matching and single-delivery/idempotency behavior
- Docs updated (`docs/agents.md`, `docs/dashboard-guide.md`) to describe the one-time notice and its dedup key
- Changeset added for `@runfusion/fusion` (minor, feature)
Files changed:
.changeset/fn-7879-postgres-migration-inbox-notice.md | 7 ++
docs/agents.md | 1 +
docs/dashboard-guide.md | 1 +
packages/cli/src/commands/daemon.ts | 6 +-
packages/cli/src/commands/dashboard.ts | 5 +
packages/cli/src/commands/serve.ts | 6 +-
packages/dashboard/src/index.ts | 2 +-
packages/engine/src/__tests__/postgres-migration-notice.test.ts | 140 +++++++++++++++++++++
packages/engine/src/index.ts | 9 ++
packages/engine/src/postgres-migration-notice.ts | 107 ++++++++++++++++
packages/engine/src/project-engine-manager.ts | 6 +
packages/engine/src/project-engine.ts | 12 ++
12 files changed, 299 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7879
Fusion-Task-Lineage: 201877e5-6bdc-4168-a8ac-ae0e50ec8308
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
- 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>
Plugin-contributed skills previously registered only a name for sessions and the dashboard, so their SKILL.md bodies were never actually loaded — fix threads real body paths through to both session creation and the Skills UI.
- Resolve each enabled plugin skill's body path via @fusion/core's resolvePluginSkillBodyPath and thread its body dir (plus parent dir) into every session-creating lane (executor primary/retry/verification-fix/step/child-agent, triage, reviewer, merger, agent-heartbeat, cron-runner) as additionalSkillPaths, unioned with existing CE skill dirs.
- Add collectPluginSkillNames/mergePluginSkills additionalSkillPaths plumbing in session-skill-context.ts so plugin skill discovery paths flow the same way as native/role-fallback skills.
- Update dashboard skills-adapter.ts to read plugin skill SKILL.md and reference files from disk (via the traversal-guarded reader) instead of returning a runtime-placeholder/"not found" response for plugin-sourced skills.
- Document the plugin skill body delivery mechanism in docs/PLUGIN_AUTHORING.md.
- Add regression coverage: plugin-skill-body-delivery.test.ts, expanded session-skill-context.test.ts and skills-adapter.test.ts.
- Add changeset fn-7857-plugin-skill-body-delivery.md (minor, fix).
Files changed:
.changeset/fn-7857-plugin-skill-body-delivery.md | 7 ++
docs/PLUGIN_AUTHORING.md | 3 +
.../dashboard/src/__tests__/skills-adapter.test.ts | 92 ++++++++++++++++------
packages/dashboard/src/skills-adapter.ts | 33 ++------
.../__tests__/plugin-skill-body-delivery.test.ts | 75 ++++++++++++++++++
.../src/__tests__/session-skill-context.test.ts | 84 +++++++++++++++++++-
packages/engine/src/agent-heartbeat.ts | 3 +-
packages/engine/src/cron-runner.ts | 2 +
packages/engine/src/executor.ts | 25 ++++--
packages/engine/src/merger.ts | 10 ++-
packages/engine/src/reviewer.ts | 2 +
packages/engine/src/session-skill-context.ts | 43 ++++++++--
packages/engine/src/step-session-executor.ts | 5 +-
packages/engine/src/triage.ts | 3 +-
14 files changed, 318 insertions(+), 69 deletions(-)
Fusion-Task-Id: FN-7857
Fusion-Task-Lineage: 9ba4c305-8b38-4ae8-85b3-4c87205ef767
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Project-scoped chat managers can be cached before the project engine boots, so fn_send_message/fn_read_messages were silently dropped for lazily-booted (desktop) sessions while browser sessions kept them; the fix refreshes the cached manager's MessageStore post-construction and surfaces a diagnostic + chat-stream warning when the reduced tool schema condition occurs instead of failing silently.
Key changes:
- ChatManager gains setMessageStore() to refresh a cached manager's MessageStore post-construction, mirroring the existing setPluginRunner() refresh seam
- getOrCreateScopedChatManager()/resolveScopedChatManager() now accept and wire an optional MessageStore, upgrading already-cached managers instead of leaving them stale
- register-chat-routes.ts now passes engine.getMessageStore() through to the scoped chat manager resolver
- ChatManager emits a new 'warning' chat-stream event (code: tool-schema-reduced) plus a diagnostics.warn() call when a bound agent has no MessageStore, so reduced tool schema is agent-visible instead of a silent per-call failure
- Added regression tests covering MessageStore wiring/refresh in chat-project-services and chat-manager, plus a patch changeset documenting the fix
Files changed:
.changeset/fn-7854-chat-tool-schema-parity.md | 7 ++
.../dashboard/src/__tests__/chat-manager.test.ts | 124 ++++++++++++++++++++-
.../src/__tests__/chat-project-services.test.ts | 67 +++++++++++
packages/dashboard/src/chat-project-services.ts | 10 +-
packages/dashboard/src/chat.ts | 39 +++++++
.../dashboard/src/routes/register-chat-routes.ts | 2 +-
6 files changed, 245 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7854
Fusion-Task-Lineage: 1d1ee3e7-608b-4b7d-be45-138b38b27f17
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
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>
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>
Grok advertises promptCapabilities.image=false and ignores ACP image
ContentBlocks (live probe: NO_IMAGE). Path-based vision works when the
agent is given an absolute file path. Include path hints in chat prompts
from .fusion/chat-attachments and carry path on ChatImageContent for
file:// uris.
Replace one-shot grok -p JSON with native grok agent stdio (ACP) for realtime
streaming, tool visibility, and multi-turn sessions. Vendor the ACP client
into fusion-plugin-grok-runtime, forward Fusion fn_* tools and operator MCP,
stage Fusion skills via --plugin-dir, authenticate per xAI headless docs, and
align project chat manager store resolution so Grok chat sessions can send.
pushAfterMerge was only implemented in the soft-deprecated legacy aiMergeTask
pipeline, so after master-plan U0 made runAiMerge the sole merge path the
setting silently did nothing and origin fell permanently behind local main.
- runAiMerge now runs a post-finalize push step: working-tree-independent
ref-to-ref push fast path; on remote divergence a detached clean-room
pull --rebase (with AI conflict resolution) pushes HEAD and CAS-advances
the local integration ref (explicit non-FF opt-in, push path only), then
runs merge-advance auto-sync and refreshes mergeDetails.commitSha.
- Push failures stay non-fatal (task finalizes done) with push:origin
run-audit events and PushToRemoteFailed task-log entries.
- Merge settings: Push Remote free-text replaced by remote + target-branch
dropdowns (Custom… escape, free-text fallback when no remotes), persisting
to the same pushRemote setting string. New GET /api/git/remotes/:name/branches
endpoint lists remote-tracking branches.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude per-model weekly usage is parsed generically from the OAuth payload's
limits[] scoped entries (live probe disproved the seven_day_fable key guess).
Grok now prefers ~/.grok/auth.json OIDC credentials against
cli-chat-proxy.grok.com/v1/billing?format=credits for a real percent-used
weekly credits window, falling back to the xAI API-key validity card.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Consolidates Cursor Admin API key resolution onto one documented env var so the usage/admin credential path is reachable and unambiguous, replacing the prior dual CURSOR_ADMIN_API_KEY/CURSOR_API_KEY and multi-provider-id lookup.
- Replace CURSOR_ADMIN_API_KEY (preferred) + CURSOR_API_KEY alias with a single CURSOR_API_KEY env var, mirroring the GROK_API_KEY precedent
- Simplify readCursorApiKey to check CURSOR_API_KEY then fall back to the single "cursor" authStorage entry via readConfiguredApiKey (drop the cursor/cursor-cli/cursor-agent provider-id loop)
- Export readCursorApiKey and fetchCursorUsage for direct test coverage
- Update the no-auth error message and settings-reference.md docs to reference only CURSOR_API_KEY, clarifying cursor-cli OAuth/session auth vs the separate Admin API usage-metering credential
- Add changeset (@runfusion/fusion: minor) documenting the credential-path change
- Add/adjust usage.test.ts coverage for readCursorApiKey precedence (env over authStorage) and the updated credential-absent error message
Files changed:
.changeset/fn-7817-cursor-api-key.md | 7 ++++
docs/settings-reference.md | 8 ++--
packages/dashboard/src/__tests__/usage.test.ts | 53 +++++++++++++++++++++++++-
packages/dashboard/src/usage.ts | 52 ++++++++-----------------
4 files changed, 77 insertions(+), 43 deletions(-)
Fusion-Task-Id: FN-7817
Fusion-Task-Lineage: 86ac3d47-8e80-4159-abee-6c41aae56407
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a Cursor provider fetcher to the dashboard usage aggregator so operators with a Cursor Admin API key see spend-based usage alongside the other providers.
- usage.ts: add fetchCursorUsage() using the Cursor Admin API POST https://api.cursor.com/teams/spend with Basic auth (API key as username), resolving the key from CURSOR_ADMIN_API_KEY (preferred) or CURSOR_API_KEY, falling back to fusion-auth/pi-configured api keys; maps teamMemberSpend overallSpendCents/spendCents and hardLimitOverrideDollars/monthlyLimitDollars into a "Monthly spend" usage window with a reset derived from subscriptionCycleStart
- usage.ts: wire fetchCursorUsage into fetchAllProviderUsage's parallel provider fetch list (with withTimeout + no-auth demotion) and update the provider-list comment
- UsageIndicator.tsx: map the "Cursor" provider name to the existing cursor-cli icon token/SVG
- usage.test.ts: add CURSOR_ADMIN_API_KEY/CURSOR_API_KEY env stubbing and a full fetchCursorUsage regression suite (ok/zero-utilization/no-auth/error/expired-key/parse-failure cases)
- UsageIndicator.test.tsx: cover the Cursor icon mapping
- docs/settings-reference.md: document that the Usage dropdown Cursor card requires a Cursor Admin API key (session-only cursor-agent login is insufficient)
- add a minor changeset for @runfusion/fusion documenting the new Cursor usage card
Files changed:
.changeset/fn-7816-cursor-usage.md | 7 +
docs/settings-reference.md | 4 +
packages/dashboard/app/components/UsageIndicator.tsx | 7 +
packages/dashboard/app/components/__tests__/UsageIndicator.test.tsx | 26 +++
packages/dashboard/src/__tests__/usage.test.ts | 157 ++++++++++++++
packages/dashboard/src/usage.ts | 240 ++++++++++++++++++++-
6 files changed, 440 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7816
Fusion-Task-Lineage: 4cec63d8-4ddc-40f3-8d16-5e4078da5eba
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Adds a Grok (xAI) provider fetcher to the dashboard's usage aggregation so a Grok card now appears in the Usage dropdown when credentials are configured.
- Add fetchGrokUsage() in usage.ts: resolves the API key from GROK_API_KEY env, then ~/.grok/user-settings.json, then grok-cli auth storage, and validates it against GET https://api.x.ai/v1/api-key
- Since xAI exposes no subscription usage meter for inference keys, the card reports auth-validity status (ok/no-auth/error) with an empty usage-window list rather than fabricating quota data
- Surfaces clear error messages for expired/blocked keys and non-200 responses; omits the card entirely when no credentials are found
- Register fetchGrokUsage in fetchAllProviderUsage's parallel provider fetch list alongside Claude, Codex, Gemini, Minimax, Zai, and GitHub Copilot
- Add extensive test coverage in usage.test.ts for key-source precedence, ok/error/no-auth states, and blocked/expired key handling
- Add changeset (.changeset/fn-7814-grok-usage.md) documenting the new minor feature
Files changed:
.changeset/fn-7814-grok-usage.md | 7 ++
packages/dashboard/src/__tests__/usage.test.ts | 157 +++++++++++++++++++++++++
packages/dashboard/src/usage.ts | 95 ++++++++++++++-
3 files changed, 257 insertions(+), 2 deletions(-)
Fusion-Task-Id: FN-7814
Fusion-Task-Lineage: cac497a9-5a57-4ba5-a7ea-8a01b89a0cbd
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
- contain fn_artifact_register path payloads: realpath-canonicalized
containment before stat/read — relative paths require and must stay
inside baseDir, absolute paths allowed only under baseDir or the OS
temp dir (deliberate allowance for browser/screenshot tooling);
the process.cwd() fallback is gone, symlink escapes rejected
- bind task-scoped heartbeat artifact registration to the acquired
worktree (baseDir: sessionCwd rebind after acquisition); no-task
heartbeat prompt now says to pass absolute temp-dir paths
- enforce exactly-one payload source (content/uri/dataBase64/path);
content+uri combos are now rejected to match the documented contract
- add FNXC rationale comments at both visual-artifact instruction sites
in the planning prompts (sync contract with the executor prompt)
- media route: statSync -> await stat from node:fs/promises
- range tests ride the in-memory MockSocket harness (TestResponse gains
binary-safe bodyBuffer; real-TCP helper deleted) and assert the full
206 Content-Range/Content-Length contract for every range form
- add PdfViewer coverage (iframe src/title) in DocumentsView tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Video was registrable but effectively unusable, and HTML/PDF deliverables
had no first-class path from agents to the gallery.
- media route now serves HTTP byte ranges (Accept-Ranges, 206 +
Content-Range, 416 on unsatisfiable) so <video>/<audio> seeking works
and Safari plays media at all
- video attachments (mp4/webm/mov, 100MB cap vs 5MB for other types)
bridge into the artifact registry like images; multer transport ceiling
raised to 100MB with per-type caps enforced in the store
- fn_artifact_register path payloads are signature-validated for video
(ftyp box / EBML header) and PDF (%PDF- prefix), mirroring images
- HTML doc artifacts (mimeType text/html) render as live sandboxed
iframe previews by default in the doc viewer, with a Preview/Source
toggle and the same FileEditor edit mode
- executor/heartbeat/planning prompts and tool descriptions now cover
the full type matrix: images, videos, audio, HTML mockups, PDFs, and
markdown docs, each with the registration recipe
Verified live: range requests (200/206/416) via curl, an ffmpeg-generated
mp4 playing to completion in the gallery lightbox, and an interactive
HTML mockup rendering in the sandboxed preview.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Agents could never get screenshots/wireframes/mocks into the Artifacts view:
fn_artifact_register was gated on assignedAgentId (never set in default
ephemeral mode), the only image payload source was inline base64, and no
prompt ever told agents to register visual deliverables.
- always expose fn_artifact_register to executor sessions ("executor" author
fallback), resolve relative paths against the task worktree, and default
taskId to the executing task (heartbeat task lane too)
- add a `path` payload source: file read with 50MB cap, extension MIME
inference, PNG/JPEG/GIF/WebP signature + SVG sniff validation, persisted
through managed artifact storage
- executor/heartbeat/planning prompts + engine-tools reference now instruct
agents to register screenshots, wireframes, mockups, and recordings
- new ArtifactsGallery: Images/Docs/PDFs/Videos/Audio/Other category sections
and filter chips, visual tile grid + lightbox, embedded PDF viewer, audio
player rows, download rows; mobile-responsive down to the 768px breakpoint
- doc artifacts open a full viewer rendered as markdown by default with an
in-place edit mode using the shared CodeMirror FileEditor; persisted via new
GET/PATCH /api/artifacts/:id + TaskStore.updateArtifact and live-refreshed
through the new artifact:updated SSE event
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reclassifies Gemini usage fetch outcomes so unconfigured/unauthenticated Gemini no longer shows a noisy error card in the usage dropdown; transient failures of a configured token still surface as errors.
- fetchGeminiUsage() in packages/dashboard/src/usage.ts now sets status to `no-auth` (instead of `error`) for unsupported auth types (api-key/vertex-ai) and for HTTP 401/403 auth-expired responses, so fetchAllProviderUsage omits Gemini from the aggregate list in those cases
- HTTP 5xx, network, timeout, and parse failures for a configured Gemini token remain `error` and visible, per the existing FN-7798 keep-auth-expired-visible convention for other providers
- Added FNXC:UsageProviders comments documenting why Gemini deliberately diverges from that convention
- Updated packages/dashboard/src/__tests__/usage.test.ts to cover the new no-auth classification
- Added changeset .changeset/fn-7806-gemini-usage.md (patch) documenting the fix for release notes
Files changed:
.changeset/fn-7806-gemini-usage.md | 7 +
packages/dashboard/src/__tests__/usage.test.ts | 211 +++++++++++++++----------
packages/dashboard/src/usage.ts | 15 +-
3 files changed, 148 insertions(+), 85 deletions(-)
Fusion-Task-Id: FN-7806
Fusion-Task-Lineage: e86b23ea-14e9-472d-8b44-3951fa02ae6c
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
- store.ts: guard parseStepsFromPrompt in listTasks and searchTasks too, so one
unreadable PROMPT.md can't reject the Promise.all and 500 the whole board
list/search (CodeRabbit). Matches the getTask fallback.
- update-check.ts: isHomebrewInstall now resolves symlinks and matches the real
Cellar/opt install roots, fixing Intel-macOS Homebrew detection that only
checked /usr/local/Homebrew/ (brew's repo dir) and would have shown npm/sudo
guidance instead of `brew upgrade` (CodeRabbit).
- task-detail-prompt-resilience.test.ts: extend to assert the invariant across
all surfaces — listTasks(slim)/searchTasks, reopen-to-todo moveTask
(resetPromptCheckboxes), and deleteTask — not just getTask/updateTask/archive
(CodeRabbit; Surface Enumeration rule).
- serve.test.ts: add SIGINT/SIGTERM exit-code assertions (130/143) so the serve
path's POSIX exit contract can't regress independently of daemon (CodeRabbit).
- update-check.test.ts: add Intel-Homebrew remediation test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#5 root cause (reproduced): getTask — the shared load for the entire per-task
API — plus the mutation helpers updateTaskUnlocked, updateStep,
readPromptForArchive, and resetPromptCheckboxes all read PROMPT.md unguarded.
An unreadable PROMPT.md (root-owned from a prior `sudo` run -> EACCES, PROMPT.md
being a directory -> EISDIR, transient FS error) threw and 500'd every per-task
operation (GET/DELETE/PATCH/retry/reset/archive) for every task, while the
PROMPT.md-free board list and create kept working. These reads are now
best-effort: degrade (empty prompt / unsynced steps / skipped cosmetic sync)
and log, so a PROMPT.md hiccup can never brick task management. Added a symptom-
verification test that forces EISDIR and asserts getTask/updateTask/archiveTask
still succeed.
#10c: the dashboard badge-snapshot cache only evicted on hard-delete, so
archived tasks were re-cached via task:updated and retained for the daemon's
lifetime — a slow memory leak. New isBadgeEligibleTask predicate gates the
create/update listeners so archived tasks are evicted (matching the startup
prime's includeArchived:false). Added a unit test for the invariant.
Updates the #5 changeset to cover the real fix; adds a badge-eviction changeset.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses three user-reported bugs:
- API 500 diagnosability: rethrowAsApiError now preserves the original error
as Error `cause` and the /api boundary logs stack + cause for 5xx, so the
opaque "task write API returns 500 for every task" failures are traceable
(client body stays generic in production).
- In-app "Update now": detect EACCES/EPERM install failures and return
actionable remediation (sudo fn update / reinstall without sudo / brew
upgrade) instead of raw npm stderr; do not retry --force for this class.
- Daemon restart: `fn daemon` and `fn serve` exit 128+signal (SIGTERM=143,
SIGINT=130) on signal-initiated shutdown so Restart=on-failure restarts a
memory-pressure kill. Interactive `fn dashboard` TUI intentionally unchanged.
Adds regression tests (update-check EACCES/EPERM, daemon exit codes) and three
@runfusion/fusion patch changesets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Only show usage meters for AI providers the user has actually configured, instead of surfacing entries for providers with no meterable data.
- fetchGitHubCopilotUsage now demotes GitHub's 404 "No Copilot subscription found" response (both the Fusion-credential HTTP path and the gh-CLI fallback path) to a `no-auth` status instead of `error`, so it is treated as no meterable entitlement.
- fetchAllProviderUsage's existing `status !== "no-auth"` filter now also excludes these no-entitlement Copilot results, so they no longer appear in the usage list.
- Configured-but-failing providers (expired auth returning 401/403, transient HTTP 5xx, or other errors) keep `status: "error"` and remain visible with their diagnostic message.
- Added regression tests covering: Fusion-credential 404 omitted, Fusion-credential 500 surfaced as error, gh-CLI 404 omitted, gh-CLI 401 surfaced as "GitHub auth expired" error.
- Added a changeset documenting the usage-view behavior change as a patch/fix.
Files changed:
.changeset/fn-7798-usage-configured-providers.md | 7 +++
packages/dashboard/src/__tests__/usage.test.ts | 71 ++++++++++++++++++++++--
packages/dashboard/src/usage.ts | 19 ++++++-
3 files changed, 90 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-7798
Fusion-Task-Lineage: 18835b48-68f3-48aa-a03c-cc85772778a9
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The reliability GET/reset handlers referenced getScopedStore, which is
defined inside setupBadgeWebSocket and is not visible in the createServer
scope where these handlers live — so the scoping change did not typecheck.
Switch to the in-scope resolveProjectScopedStore helper (used by the other
realtime endpoints), which also routes through engineManager for correct
per-project resolution.
Guard store resolution with try/catch returning a targeted 500, mirroring
the project SSE handler, instead of falling through to the generic error
handler. Add project-scoping regression tests: GET reads the project store,
GET without projectId falls back to root, and reset writes the project store.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reliability GET and reset endpoints were always using the server's
root store, ignoring projectId. Mirror the Command Center pattern by
using getProjectIdFromRequest and getScopedStore so multi-project
servers report per-project reliability stats.
Refs FUX-042
Grok CLI chat failed two different ways depending on the surface:
1. Default (no-project) chat errored with "requires the bundled Grok CLI
runtime". The default ChatManager was handed a bare PluginLoader, but Grok
routing (deriveGrokRuntimeHintForNoVisibleKey -> resolveRuntime) needs a
PluginRunner's getRuntimeById/createRuntimeContext; the unguarded call threw
"getRuntimeById is not a function". New resolveChatManagerPluginRunner()
prefers the engine's PluginRunner (same runner the project-scoped path uses),
falling back to the loader only in UI-only mode.
2. Project-scoped chat returned empty replies. The CLI-bundled Grok plugin
(packages/cli/dist/plugins/.../bundled.js, gitignored) was stale vs the
FN-7796 single-JSON adapter source; the running server loads that bundle,
not the plugin's own dist. `pnpm build` regenerates it. Noted in the
changeset that the freshness guard only warns and the dev prebuild does not
rebuild the CLI tsup bundle.
Verified end-to-end on a live dashboard: both default and project-scoped
grok-cli/grok-4.5 chats now stream thinking + text.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Image attachments added via TaskStore.addAttachment now surface as first-class image artifacts, reusing the existing artifact listing/SSE/media pipeline instead of duplicating bytes.
- addAttachment() registers a URI-only "image" artifact (metadata.source: "attachment") pointing at the already-written attachments/<file> path whenever an image mimeType is attached; registration is best-effort and swallows the archived/soft-deleted-task rejection so addAttachment keeps its always-succeeds contract for valid images.
- deleteAttachment() now removes any bridged artifact rows for a filename before deleting the attachment file, so /api/artifacts/:id/media can never point at a deleted attachment.
- register-task-workflow-routes.ts's resolveArtifactMediaPath now accepts task-scoped attachments/<file> URIs (in addition to artifacts/<file>) so the media route can stream bridged image-attachment artifacts; task-less artifacts remain restricted to .fusion/artifacts/.
- docs/storage.md documents the attachment→artifact bridge behavior and the media route's accepted URI prefixes.
- Added a changeset (@runfusion/fusion: minor) describing the user-facing Artifacts view change.
- Extended store-attachments and artifacts-route-integration tests to cover the new bridging and deletion behavior.
Files changed:
.changeset/fn-7791-image-attachments-artifacts.md | 7 +++
docs/storage.md | 3 +-
packages/core/src/__tests__/store-attachments.test.ts | 59 +++++++++++++++++++++-
packages/core/src/store.ts | 58 ++++++++++++++++++++-
packages/dashboard/src/routes/__tests__/artifacts-route-integration.test.ts | 40 +++++++++++++++
packages/dashboard/src/routes/register-task-workflow-routes.ts | 9 +++-
6 files changed, 172 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7791
Fusion-Task-Lineage: 4df47880-6161-4a8b-933a-2f6fc2fed953
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes plugin skills silently disappearing when the fn daemon is started outside the project that enabled the contributing plugin, by making skill resolution project-aware instead of scoped to the daemon's root PluginLoader.
- getPluginSkills now resolves per requesting rootDir against project_plugin_states rather than the daemon-root PluginLoader scope
- Plugins skipped as disabled are now logged at load time for visibility
- Wired the new project-aware resolution through dashboard.ts, serve.ts, and daemon.ts CLI commands
- Added regression coverage in plugin-loader.test.ts and skills-adapter.test.ts
- Documented the project-scoped behavior in docs/PLUGIN_AUTHORING.md and docs/agents.md
- Added a patch changeset for @runfusion/fusion
Files changed:
.changeset/fn-7778-plugin-skills-project-scope.md | 7 +++
docs/PLUGIN_AUTHORING.md | 2 +
docs/agents.md | 2 +-
packages/cli/src/commands/daemon.ts | 68 +++++++++++++++++++--
packages/cli/src/commands/dashboard.ts | 71 ++++++++++++++++++++--
packages/cli/src/commands/serve.ts | 68 +++++++++++++++++++--
packages/core/src/__tests__/plugin-loader.test.ts | 69 +++++++++++++++++++++
packages/core/src/plugin-loader.ts | 29 ++++++---
.../dashboard/src/__tests__/skills-adapter.test.ts | 29 +++++++++
packages/dashboard/src/skills-adapter.ts | 19 ++++--
10 files changed, 337 insertions(+), 27 deletions(-)
Fusion-Task-Id: FN-7778
Fusion-Task-Lineage: 5d9a8ff2-ed0e-4859-bf9c-a16f715b081d
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fix useArtifacts fetching/subscribing only when a projectId is present, which left the Artifacts tab stuck at 0 on single-project dashboards where currentProject is unset at mount.
- useArtifacts now builds a cache key and fetches/subscribes even without a projectId, scoping the cache under a __default__ key
- SSE subscription omits the projectId query param when unset (default/unscoped /api/events) and only filters incoming events by projectId when one is set
- Added/updated tests covering the default-scope fetch, cache, and SSE subscription paths
- Added a changeset documenting the fix
Files changed:
.changeset/fn-7767-artifacts-default-scope.md | 7 ++++
.../app/hooks/__tests__/useArtifacts.test.ts | 42 +++++++++++++++++---
packages/dashboard/app/hooks/useArtifacts.ts | 37 ++++++------------
.../__tests__/artifacts-route-integration.test.ts | 45 ++++++++++++++++++++++
4 files changed, 100 insertions(+), 31 deletions(-)
Fusion-Task-Id: FN-7767
Fusion-Task-Lineage: b4ea9b1f-2908-4f5b-bf75-d6fdc45f9340
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fix a "Response failed" crash for plugin CLI runtime chats (grok/droid/cursor) whose sessions expose top-level `messages` and stream via `onText` without a pi-shaped `session.state`.
- Read messages/errorMessage null-safely from `session.state`, falling back to top-level `session.messages` when state is absent, in both the room responder and streaming response extraction paths
- Keep `state.errorMessage` optional so successful streams from state-less sessions no longer throw TypeErrors, while pi/openclaw/hermes provider errors still surface correctly
- Add regression tests covering state-less plugin CLI sessions in chat-manager.test.ts
- Add changeset for the fix
Files changed:
.changeset/fn-7765-grok-cli-chat-crash.md | 7 ++
packages/dashboard/src/__tests__/chat-manager.test.ts | 90 ++++++++++++++++++++++
packages/dashboard/src/chat.ts | 35 ++++++---
3 files changed, 121 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-7765
Fusion-Task-Lineage: 80e33a97-e971-4aec-a4cf-29d97c5c5e62
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Extend artifact test coverage to pin creation, listing, and viewing across every artifact type and payload variant on both the agent/dashboard-chat tool surface and the dashboard artifacts route.
- Add a route-level integration test covering list/serve for all artifact types (document, image, video, audio, other) across inline content, uri reference, and binary data payloads, including task-scoped filtering, registry-level (task-less) artifacts, and 404 behavior for uri-only artifacts requested via /media.
- Add an engine-level real-TaskStore test exercising fn_artifact_register/list/view (agent tools) and the dashboard-chat artifact tool for every artifact type and content/uri/dataBase64 variant, asserting list and view output correctness.
- Factor out shared PNG_IMAGE_BYTES fixture and per-type MIME/binary fixtures to keep new assertions concise.
Files changed:
.../__tests__/artifacts-route-integration.test.ts | 150 ++++++++++++++++++++-
.../src/__tests__/agent-artifact-tools.test.ts | 142 ++++++++++++++++++-
2 files changed, 287 insertions(+), 5 deletions(-)
Fusion-Task-Id: FN-7764
Fusion-Task-Lineage: 187b3f0f-d1b4-42fe-9658-1ee67870b524
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>