Add an interactive, worktree-rooted, multi-tab Terminal tab to the Task Detail
view, distinct from the pre-existing CLI-agent Session tab.
- TaskDetailModal gains a new embedded Terminal tab (single non-workspace
task with one recorded worktree) that mounts TerminalModal in a new
`embedded` render mode, rooted at the task's worktree
- Rename the existing agent-session tab label to "Session" to disambiguate
it from the new Terminal tab
- useTerminalSessions gains task-scoped session storage and a `defaultCwd`
option so embedded terminal tabs persist separately from footer/global
project terminal tabs and start in the task worktree
- TerminalModal/CSS updated to support the embedded layout mode
- Update lazy-loaded-views docs test and AGENTS.md exclusion list to cover
the new `LazyTerminalModal` task-detail-internal surface
- Document the new Session/Terminal tab split in docs/dashboard-guide.md
- Add i18n strings for the new Terminal tab across all locales
- Add a changeset (minor) for @runfusion/fusion
Files changed:
.changeset/FN-7813-worktree-terminal-tab.md | 7 +
AGENTS.md | 2 +-
docs/dashboard-guide.md | 3 +
.../app/__tests__/lazy-loaded-views-docs.test.ts | 4 +-
.../dashboard/app/components/TaskDetailModal.css | 17 +++
.../dashboard/app/components/TaskDetailModal.tsx | 41 +++++-
.../dashboard/app/components/TerminalModal.css | 51 +++++++
.../dashboard/app/components/TerminalModal.tsx | 71 +++++++---
.../__tests__/TaskDetailModal.test-helpers.ts | 3 +
.../TaskDetailModal.worktree-terminal.test.tsx | 139 ++++++++++++++++++
.../components/__tests__/TerminalModal.test.tsx | 29 ++++
.../hooks/__tests__/useTerminalSessions.test.ts | 157 +++++++++++++++++++++
.../dashboard/app/hooks/useTerminalSessions.ts | 63 ++++++---
packages/i18n/locales/en/app.json | 3 +-
packages/i18n/locales/es/app.json | 3 +-
packages/i18n/locales/fr/app.json | 3 +-
packages/i18n/locales/ko/app.json | 3 +-
packages/i18n/locales/zh-CN/app.json | 3 +-
packages/i18n/locales/zh-TW/app.json | 3 +-
19 files changed, 550 insertions(+), 55 deletions(-)
Fusion-Task-Id: FN-7813
Fusion-Task-Lineage: 4ef86a15-347a-4862-b01c-5063d8004cb8
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>
Adds select-to-comment parity to the Task Documents right pane in the Artifacts view, reusing the existing Project Files selection-comment pattern (useSelectionComment/SelectionCommentPopover) so operators can highlight task-document content and send it to a new task.
- Add markdown/plain preview refs and useSelectionComment hooks scoped to the selected Task Document, following the existing Plain/Markdown render toggle
- Gate the Task Documents selection popover on activeTab === "tasks" and the selected task document (separate from the Project Files popover, which stays gated on activeTab === "project") so tab switches never cross-render popovers
- Compose the New Task description source as `taskId/key` for task-document selections, mirroring the file-path convention used for Project Files
- Add regression tests covering plain/markdown task-document selection, empty-pane gating, tab isolation between Task Documents and Project Files popovers, and the mobile detail pane
- Update dashboard-guide.md to document select-to-comment support for Task Documents alongside Project Files
- Add a minor changeset for @runfusion/fusion documenting the feature (depends on FN-7811)
Files changed:
.changeset/fn-7812-task-documents-select-to-comment.md | 7 ++
docs/dashboard-guide.md | 4 +-
packages/dashboard/app/components/DocumentsView.tsx | 37 ++++++---
packages/dashboard/app/components/__tests__/DocumentsView.test.tsx | 93 ++++++++++++++++++++++
4 files changed, 130 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-7812
Fusion-Task-Lineage: ace10d70-35fd-42fe-8a4a-e2c2c7c7c27b
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Reworks the Artifacts view's Task Documents tab to reuse the Project Files left-sidebar/right-pane layout instead of expandable task-grouped cards, giving both tabs a consistent browsing pattern.
- Replace the collapsible TaskGroup/DocumentCard components with a documents-project-layout sidebar listing documents grouped by task (with revision metadata and task status badges) and a right pane rendering the selected document's content
- Add separate selectedTaskDocumentId selection state so tab switching never leaks Project Files content into Task Documents (and vice versa), with desktop/mobile gating matching the Project Files pattern
- Preserve the existing Plain/Markdown render toggle for task document content; select-to-comment stays Project-Files-only for this change (tracked as follow-up)
- Update DocumentsView.css for the new sidebar/right-pane structure and rewrite DocumentsView.test.tsx coverage for the new interaction model
- Update docs/dashboard-guide.md to describe the shared sidebar/right-pane browsing pattern for Task Documents
- Add changeset fn-7811-task-documents-sidebar.md (minor)
Files changed:
.changeset/fn-7811-task-documents-sidebar.md | 7 +
docs/dashboard-guide.md | 4 +-
.../dashboard/app/components/DocumentsView.css | 181 ++++--------
.../dashboard/app/components/DocumentsView.tsx | 308 ++++++++++-----------
.../components/__tests__/DocumentsView.test.tsx | 116 ++++++--
5 files changed, 299 insertions(+), 317 deletions(-)
Fusion-Task-Id: FN-7811
Fusion-Task-Lineage: e943ebf8-6e8e-4f5f-a55c-99c949be2624
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fixes the List view's two-pane split layout clipping primary controls and the
expanded quick-add composer on tablet-width viewports (769-1024px) by
switching those widths to the same single-pane card/detail layout already
used on mobile.
- Add `useSinglePaneList` gate in ListView.tsx (`viewportMode === "mobile" || "tablet"`) driving split-vs-single-pane structure, detail routing, and resize-handle wiring, while touch-only long-press behavior stays on `isMobile`.
- Extend the mobile-only responsive CSS breakpoints in ListView.css from `max-width: 768px` to `max-width: 1024px` so tablet gets the same toolbar/card scaffolding as mobile, while desktop split rules remain unchanged above that tier.
- Add `list-view--single-pane` root class and route tablet clicks through the single-pane `onOpenDetail` path instead of the desktop split-pane selection.
- Update docs/dashboard-guide.md to describe the tablet single-pane behavior and add an FNXC:ListView comment recording the FN-7809 rationale.
- Add regression tests covering tablet single-pane rendering, tablet detail-open routing, and updated CSS-fixture assertions for the widened breakpoint.
- Add a patch changeset describing the fix for @runfusion/fusion release notes.
Files changed:
.changeset/fn-7809-list-tablet-single-pane.md | 7 +++
docs/dashboard-guide.md | 6 +-
packages/dashboard/app/components/ListView.css | 11 ++--
packages/dashboard/app/components/ListView.tsx | 49 ++++++++-------
.../app/components/__tests__/ListView.test.tsx | 69 +++++++++++++++++++++-
5 files changed, 112 insertions(+), 30 deletions(-)
Fusion-Task-Id: FN-7809
Fusion-Task-Lineage: 9e5f8bb7-13ee-4a58-81b1-eb5a9911bd4e
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Replace the FileEditor's bounded self-echo Set with monotonic edit versions so trailing-newline edits (e.g. after a markdown header or at end-of-file) are never dropped when an older content prop races the live CodeMirror document.
- Track a monotonic localEditVersionRef and a contentEditVersionsRef map (content -> version) instead of a size-capped Set of echoed strings, avoiding eviction of stale values during long editing sessions.
- Compare a stale prop's known edit version against the live document's version and the last accepted prop version to decide if it is a stale self-echo, at any session length.
- Update FNXC:FileViewer comment to document the FN-7810 rationale for switching from bounded-Set self-echo detection to edit-version comparison.
- Add regression tests covering trailing markdown-header and plain-text newline edits across markdown/non-markdown file paths, and a long-session (20+ edit) case that previously could evict the self-echo tracking.
Files changed:
packages/dashboard/app/components/FileEditor.tsx | 32 +++++++----
.../app/components/__tests__/FileEditor.test.tsx | 64 +++++++++++++++++++++-
2 files changed, 83 insertions(+), 13 deletions(-)
Fusion-Task-Id: FN-7810
Fusion-Task-Lineage: 9dd68746-e772-4fa3-b648-6f3138d1e36e
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
A long-lived connection can go SQLITE_NOTADB ('file is not a database' on
every query) while the on-disk file stays intact — observed 2026-07-10 on the
live dashboard, which then failed every API request and poll cycle until the
process was restarted, because all corruption recovery ran at open time only.
The sqlite adapter now detects connection-corruption errors, closes the dead
handle, reopens the same path, replays connection-scoped PRAGMAs, verifies
with quick_check, and retries the failed operation once when outside an
explicit transaction. Prepared statements are generation-tracked and
re-prepare transparently after a reopen; a lost transaction's unwind is
absorbed so the original error propagates cleanly. Reopens are rate-limited,
and real on-disk corruption still defers to the open-time recovery machinery.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
- artifact viewers (image/video lightbox, PDF viewer, doc viewer) now host in
the shared FloatingWindow: draggable by the viewer header, resizable by
edge/corner handles, geometry persisted per viewer kind, closed by button
or Escape; FloatingWindow gains an ariaLabel prop so headerless windows
keep an accessible dialog name
- the Artifacts view leads with the Artifacts tab and always lands on it;
Project Files and Task Documents are secondary tabs (the old auto-select
effect is gone)
- mobile tab buttons rendered at mismatched heights (two-line "Project
Files"/"Task Documents" grew past 44px while one-line "Artifacts" stayed
at 44px); tabs now pin to the uniform 44px control height with
non-wrapping labels in a scrollable row
Verified live: window dragged (264,146 -> 144,164) and resized
(1024x720 -> 872x618) in a real browser; mobile 390x844 lands on Artifacts
with uniform tab buttons.
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>
Prevent the controlled FileEditor from clobbering fast local typing (including newlines) with a stale content prop echoed back from an async parent update loop.
- Track the last emitted content and a small rolling set of recently emitted self-echoes in FileEditor.
- Skip re-syncing the CodeMirror document when the incoming content prop matches a known self-echo instead of a genuinely external change.
- Clamp the previous selection into the new document length when an external content change is applied, so the caret stays valid.
- Add regression tests covering stale self-echo renders (markdown and non-markdown files) and external content changes with clamped caret preservation.
Files changed:
packages/dashboard/app/components/FileEditor.tsx | 34 +++++++++++++--
.../app/components/__tests__/FileEditor.test.tsx | 49 ++++++++++++++++++++++
2 files changed, 80 insertions(+), 3 deletions(-)
Fusion-Task-Id: FN-7808
Fusion-Task-Lineage: 08d4f826-f637-428c-8b67-dacdb878bf4a
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
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>
Mobile Settings footer now shows a compact "vX.Y.Z" version label instead of the full "Version X.Y.Z" wording, freeing horizontal space on small viewports while desktop/tablet keep the full label.
- SettingsModal renders settings.footer.versionShort ("v{{version}}") when viewportMode === "mobile", falling back to the existing settings.footer.version label otherwise
- Added settings.footer.versionShort translation key to en and fr locale files
- Updated settings-mobile tests: renamed/adjusted the mobile version test to assert the compact label and added a new test asserting the full label is kept outside the mobile viewport
- Added a patch changeset documenting the fix
Files changed:
.changeset/fn-7804-mobile-settings-version.md | 7 +++++++
packages/dashboard/app/components/SettingsModal.tsx | 10 +++++++++-
.../app/components/__tests__/settings-mobile.test.tsx | 18 +++++++++++++++---
packages/i18n/locales/en/app.json | 1 +
packages/i18n/locales/fr/app.json | 1 +
5 files changed, 33 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-7804
Fusion-Task-Lineage: 3ff5af53-3242-44b8-b8d8-6b81f416a333
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Two store-comments refinement tests located the auto-created refinement task by
`title.includes("Refinement")`, but FN-7165 (34efa8b89) intentionally changed
refinement titles to `${sourceId}: ${feedback}` for traceability. The refinement
task is still created correctly (the length assertion passes); only the
identifying substring was stale, so the tests failed on `expect(refinement)
.toBeDefined()`. Match the refinement by its feedback-derived title instead.
No product behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
updateTask now performs the explicit updates.prompt write (and its File Scope
validation) BEFORE committing the task row / task.json, instead of after. A
failed prompt write (EACCES/EISDIR/disk-full) or invalid File Scope now aborts
the whole update atomically rather than leaving the field changes committed with
a stale PROMPT.md and returning an error — the write counterpart to the
read-resilience guards in getTask/updateStep. Adds an atomicity regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a task's steps live only in an unreadable PROMPT.md, updateStep still can't
resolve step 0 — but it now throws an error naming PROMPT.md as the cause instead
of a misleading "task has 0 steps". The operation genuinely cannot succeed (no
step data), so this is accurate reporting, not a silent success. The reachable
reset path (which derives its loop from getTask's steps) remains unaffected.
Adds a test asserting the PROMPT.md-attributed error.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
Replace the boolean isGitRepository() check with a tri-state Git detection so environmental git failures (dubious ownership, missing git binary, timeouts) are no longer misreported as "not a Git repository", which previously blocked all task execution in valid repos and survived engine restarts.
- Add detectGitRepository() in worktree-pool.ts returning repo / not-repo / error (with reason: dubious-ownership, git-missing, timeout, unknown), classified from git's stderr; bound the git rev-parse call with a 10s timeout and maxBuffer; keep isGitRepository() as a backward-compatible wrapper
- Route the executor dispatch preflight guard through detectGitRepository(): only emit the original "not a Git repository / run git init" fatal on a positive not-repo verdict; on error, throw a distinct accurate error naming the real git failure, including the safe.directory remedy for dubious ownership
- Route the in-process runtime startup warning through the same tri-state detection so it only warns "not a Git repository" on a positive not-repo verdict
- Add a regression test locking extractWorktreeConflictInfo() to NOT misclassify a dubious-ownership git worktree add failure as not-git-repo
- Add targeted tests across worktree-pool, executor-worktree, and in-process-runtime test suites covering repo/not-repo/dubious-ownership/git-missing/timeout classifications on Windows OneDrive-style and POSIX paths
- Add changeset and a docs/solutions/logic-errors write-up of the false-negative root cause and fix
Files changed:
.changeset/fn-7799-git-detection-false-negative.md | 7 +++
.../logic-errors/git-detection-false-not-repo.md | 54 ++++++++++++++++
.../engine/src/__tests__/executor-worktree.test.ts | 61 +++++++++++++++++++
.../engine/src/__tests__/worktree-pool.test.ts | 71 +++++++++++++++++++---
packages/engine/src/executor.ts | 38 +++++++++---
.../runtimes/__tests__/in-process-runtime.test.ts | 53 ++++++++++++++--
packages/engine/src/runtimes/in-process-runtime.ts | 16 ++++-
packages/engine/src/worktree-pool.ts | 66 ++++++++++++++++++--
8 files changed, 334 insertions(+), 32 deletions(-)
Fusion-Task-Id: FN-7799
Fusion-Task-Lineage: 25a84283-bf47-472b-8a98-a10bf7e494de
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fix ProviderIcon so Grok model-shaped provider strings (e.g. grok-4.5, grok-cli/grok-4-fast) render the xAI brand icon instead of falling back to the generic Cpu icon.
- ProviderIcon now falls back to inferProviderIconKey(provider) when an exact providerConfig lookup misses, before defaulting to the Cpu icon
- Added a changeset (patch) documenting the fix for dashboard provider icon surfaces
- Added regression tests covering exact xAI/Grok keys, inferred Grok model-id strings, non-Grok inferred providers (e.g. gpt-5.5), and confirming unknown/empty providers still fall back to Cpu without the xAI icon
Files changed:
.changeset/fn-7801-grok-xai-logo.md | 7 ++++++
packages/dashboard/app/components/ProviderIcon.tsx | 11 ++++++++-
.../app/components/__tests__/ProviderIcon.test.tsx | 27 ++++++++++++++++++++++
3 files changed, 44 insertions(+), 1 deletion(-)
Fusion-Task-Id: FN-7801
Fusion-Task-Lineage: e81f15f6-d295-4f6d-b21f-7cf58854457f
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Closes the durability gap behind "grok chat returns empty replies". In a source
checkout the dashboard resolved the staged CLI tsup bundle
(packages/cli/dist/plugins/<id>/bundled.js), which resolvePluginEntryPath prefers
verbatim with no freshness check. The FN-7779 dev prebuild rebuilds each plugin's
own plugins/<id>/dist but never the staged tsup bundle, so a source-only plugin
fix ran stale until a manual `pnpm build`.
getCandidatePluginDirs now probes the live workspace source dir (<repo>/plugins/<id>)
before the staged bundle, so dev loads the freshness-checked live plugin (dist-vs-src),
self-healing even when the prebuild is skipped. The global-staged dir stays first,
so published installs (no workspace dir) are unaffected — asserted by the retained
global-install regression test plus a new source-checkout preference test.
Verified on a live dashboard: grok chat streams text and the loader now writes its
reload copies under plugins/fusion-plugin-grok-runtime/dist, not the staged bundle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two failure modes wedged the PR-based merge flow (dormant under the
current direct-merge config, but the rollback target):
1. Empty-diff branches ("No commits between ...") were marked `failed`,
pinning the serial merge slot + file leases on a task that is a
legitimate no-op. Now finalized as a terminal DONE via
`finalizeNoOpMergeTask`, mirroring the engine's canonical
`noOpResult` decision (merger-ai.ts).
2. A stale-base PR that GitHub reports CONFLICTING never becomes
mergeable on its own (nothing in the PR path rebases the head), so
`awaiting-pr-checks` waited unbounded — no escape, because the PR
path never incremented `mergeRetries`. Now each conflicting poll
counts against `mergeRetries`, so the existing
`getInReviewStallReason` "merge-retries-exhausted" escape disposes
the task after `maxAutoMergeRetries` cycles. Pending/behind PRs
still wait (checks legitimately run; "behind" is fixed by rebase).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Greptile/CodeRabbit review of the chat slash-command framework:
- Composer-wipe race: ChatView and TaskPlannerChatTab cleared the composer
inside the command's success callback, silently wiping any text the user
typed while the command was in flight. Clear on submit (before the network
round-trip) instead — consistent with normal chat send, which also clears
immediately and does not restore on failure.
- Attachments were silently dropped when dispatching a slash command in
ChatView (clearing the composer revokes staged attachment URLs). Block
dispatch with a warning toast when attachments are staged.
- CHAT_COMMANDS is now a readonly array; helper signatures accept
readonly ChatCommand[].
- The planner command menu (commands-only) used skill-menu aria-label/empty
copy; use command-specific copy instead.
Add regression tests: in-flight text survives command success, composer
clears on submit even on failure, attachment dispatch is blocked, and the
planner command menu uses command-specific accessible copy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a chat command registry and the /steer command for task-bound
chats. Commands are triggered by '/' and dispatch through a common
match/dispatch path alongside existing skills autocomplete.