Merge branch 'main' into fix/transient-auth-retry
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Add a workflow setting to disable automatic large-task triage splitting.
|
||||
category: feature
|
||||
dev: Adds triageProactiveSubtaskSplittingEnabled while preserving explicit breakIntoSubtasks requests.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Show active Plan Review progress on triage task cards.
|
||||
category: fix
|
||||
dev: TaskCard now renders the existing progress affordance for Triage only when unified progress has active workflow work.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Remove the eye icon markdown/plain toggle from chat; messages always render as Markdown.
|
||||
category: breaking
|
||||
dev: Removed ChatView `chat-thread-header-render-toggle` (desktop + mobile), `showAllAsPlain` state, and `chat.showRenderedMarkdown`/`chat.showPlainText` i18n keys (FN-7541).
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Clarify the task-detail planner-oversight Nudge/Explain controls with a visible label, an in-DOM disabled reason, and an always-openable Explain panel.
|
||||
category: fix
|
||||
dev: TaskDetailModal now renders a `detail-oversight-controls-label` group label and `detail-overseer-nudge-disabled-reason` helper text (both gated by the existing oversight-cluster visibility condition); Explain no longer disables on `!canExplainOverseer` since it is read-only. Nudge's `canNudgeOverseer` gate and Stop's confirm dialog are unchanged.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Keep the task-detail Activity view menu open during mobile iOS taps.
|
||||
category: fix
|
||||
dev: Guards the Activity views dropdown against iOS visualViewport resize/scroll echoes during menu opening.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix Anthropic subscription login when pasted callback URLs contain fragment OAuth parameters.
|
||||
category: fix
|
||||
dev: Normalizes pasted OAuth callback fragments before resolving dashboard manual-code login prompts.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Stop Windows Terminal version dialogs from popping up when opening the dashboard or Settings on Windows.
|
||||
category: fix
|
||||
dev: Root cause was the worktrunk integration, not the embedded terminal: worktrunk's CLI is named `wt`, which collides with Windows Terminal (`wt.exe`) on PATH, so probing it with `wt --version` launched Windows Terminal. Fixed by (1) `useWorktrunkInstallStatus` only auto-fetching `/api/worktrunk/status` when the integration is enabled (user opt-in) instead of on every Settings/dashboard mount, and (2) an engine-level guard in `probeWorktrunk` that refuses to exec a resolved `wt` that is the Windows Terminal alias (under `WindowsApps` / a `WindowsTerminal` package dir), covering all resolution surfaces.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Expand first-run AI provider quick-start choices beyond Anthropic.
|
||||
category: feature
|
||||
dev: Moves advanced/all-provider onboarding controls under the quick-start provider section.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Select newly created folders automatically during project setup.
|
||||
category: fix
|
||||
dev: Adds DirectoryPicker opt-in selection for project-registration surfaces while preserving default picker behavior.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Show Git prerequisite guidance during first-run GitHub onboarding.
|
||||
category: feature
|
||||
dev: Adds bounded server-host git availability to auth status and onboarding.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Prevent Desktop update banners from using 0.0.0 as the current version.
|
||||
category: fix
|
||||
dev: Dashboard update checks now resolve packaged @fusion/desktop metadata and fail closed for unresolved versions.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Quit Fusion Desktop on Windows when the window is closed.
|
||||
category: fix
|
||||
dev: Updates Electron close lifecycle so Windows shutdown reaches embedded runtime cleanup.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Open desktop Anthropic Subscription OAuth logins in the system browser.
|
||||
category: fix
|
||||
dev: Adds Electron window-open policy coverage and preserves Settings auth polling completion paths.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Add GitHub OAuth and CLI setup actions to first-run onboarding.
|
||||
category: feature
|
||||
dev: GitHub onboarding now shows in-flow OAuth connect, gh auth login, and gh install guidance.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Delay GitHub setup warnings for one day and add a dashboard connect action.
|
||||
category: fix
|
||||
dev: Dashboard setup warnings now gate GitHub prompts per project and route the CTA to Settings → Authentication.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix a false AI engine not running banner in desktop mode.
|
||||
category: fix
|
||||
dev: Distinguishes transient embedded desktop engine startup from true dashboard-only mode.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Clarify the desktop Connection Manager add-remote flow.
|
||||
category: fix
|
||||
dev: Desktop Connection Manager now separates Local Server context from saved remote profiles and collapses the remote editor until add/edit.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Restore Local Server in the desktop Switch server list.
|
||||
category: fix
|
||||
dev: Desktop Connection Manager now lists local and saved remote destinations together.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Make right-dock task list clicks respect the task popup setting.
|
||||
category: fix
|
||||
dev: Threads openMobileTasksInPopup through the right-dock Tasks list route while preserving embedded dock detail when disabled.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Auto-retry retryable Code Review remediation failures.
|
||||
category: fix
|
||||
dev: Prevents retryable code-review-remediation graph failures from stranding tasks in in-review.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix no-op task branch recovery after a previously landed task.
|
||||
category: fix
|
||||
dev: Merge/recovery ownership classification now checks no-diff branches before foreign trailer rejection.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Allow documented source-free task-artifact deliveries to finish without commits.
|
||||
category: fix
|
||||
dev: fn_task_done now recognizes explicit gitignored .fusion/tasks artifact contracts while preserving source-change no-commit guards.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix direct merges so Push to remote after merge honors the configured remote and branch.
|
||||
category: fix
|
||||
dev: Resolves remote-only push targets from the merge integration branch and preserves non-fatal push errors on done tasks.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Keep task popups on the board layer with Activity menus above them.
|
||||
category: fix
|
||||
dev: Task-detail FloatingWindow callers use a lower layer band, and Activity view menus reposition after popup geometry changes.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Add configurable dashboard keyboard shortcuts for Quick Chat and Terminal.
|
||||
category: feature
|
||||
dev: Global dashboardKeyboardShortcuts settings, guarded document-level key handling, and Escape topmost-popup dismissal.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Add search in Settings so operators can find settings faster.
|
||||
category: feature
|
||||
dev: Dashboard Settings filters visible sections by setting labels and keywords.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Keep accepted chat requests waiting instead of showing false first-event timeout failures.
|
||||
category: fix
|
||||
dev: Dashboard chat POST streams no longer abort accepted-but-silent responses on the client first-event timer.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Show each task's original prompt in the Plan tab alongside the generated plan.
|
||||
category: fix
|
||||
dev: Adds a read-only Task Detail original-prompt section backed by task.description.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Add before-to-after transformation summaries to generated task definitions.
|
||||
category: feature
|
||||
dev: Built-in standard and fast triage prompts now require a `## Before → After Transformation` section.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Restore terminal Ctrl/Cmd copy and paste shortcuts.
|
||||
category: fix
|
||||
dev: Integrated and embedded terminals now own physical clipboard paste to avoid swallowed or duplicate input.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Add a pinned below-application layout option for the dashboard terminal.
|
||||
category: feature
|
||||
dev: Terminal display mode now supports persisted docked, floating, and below layouts, with header controls replacing the footer shell.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Show first-token and tool processing durations in task agent logs.
|
||||
category: feature
|
||||
dev: Adds optional agent-log timing fields `timeToFirstTokenMs` and `durationMs`.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix mobile Chat composer being hidden behind the keyboard accessory bar.
|
||||
category: fix
|
||||
dev: Adds keyboard-open bottom clearance in ChatView so the composer clears the iOS input-assistant/autofill bar without a persistent .chat-thread transform or Android reserved-gap.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Settings descriptions now show each setting's default value.
|
||||
category: feature
|
||||
dev: Appended default-value copy to settings.* i18n descriptions across Global, Runtimes, and Project Settings sections, sourced from DEFAULT_GLOBAL_SETTINGS/DEFAULT_PROJECT_SETTINGS in settings-schema.ts; added settings-default-descriptions.test.tsx guarding that every surfaced setting states a default (or explicit "inherits"/"no default \u2014 unset") and that every DEFAULT_SETTINGS key is documented or allowlisted as not surfaced.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Add a Reset Settings button to restore a menu's or all project settings to defaults.
|
||||
category: feature
|
||||
dev: New tested section→keys (scope-aware) registry (packages/dashboard/app/components/settings/section-keys.ts) drives per-menu reset via updateSettings/updateGlobalSettings with null-as-delete; non-blob sections (secrets, MCP, plugins, memory, auth, prompts, CLI agents, runtimes) are excluded with a documented reason.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Add a per-workflow planner oversight level setting (Off, Observe, Steer, Autonomous recovery).
|
||||
category: feature
|
||||
dev: New workflow setting `plannerOversightLevel` declared in BUILTIN_OVERSIGHT_SETTINGS; default `autonomous`. Per-task override and engine behavior land in follow-up tasks.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Tasks can override the workflow planner oversight level (Off, Observe, Steer, Autonomous recovery).
|
||||
category: feature
|
||||
dev: New nullable Task.plannerOversightLevel field (migration 137, SCHEMA_VERSION 137) mirroring executionMode; NULL inherits the workflow setting. Adds resolveEffectivePlannerOversightLevel precedence helper. Dashboard UI/API threading and engine behavior land in follow-up tasks.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Planner oversight now defaults to full steering/control for every workflow unless explicitly changed.
|
||||
category: feature
|
||||
dev: Confirms the `plannerOversightLevel` workflow-setting default is the highest (autonomous) level; unset workflow value and unset per-task override both resolve to full steering via `resolveEffectivePlannerOversightLevel` (task override → workflow effective value → autonomous), adding dedicated regression coverage for the "unless explicitly disabled" precedence.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Planner oversight now monitors tasks across executor, reviewer, merger, pull-request, and workflow-gate stages.
|
||||
category: feature
|
||||
dev: Adds records-only PlannerOverseerMonitor + resolveWatchedStage + OverseerStageObservation in @fusion/engine, gated by resolveEffectivePlannerOversightLevel (off = no observation) and wired into ProjectEngine via a bounded poll. Steering/recovery and UI land in FN-7512/FN-7515+.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Planner oversight can autonomously inject guidance, retry stuck/failed steps, and request fixes within bounded limits.
|
||||
category: feature
|
||||
dev: Adds pure `decidePlannerRecovery` + recovery types (core) and `PlannerRecoveryController` with injected guidance/retry/targeted-fix handlers (engine), consuming the FN-7511 observation. Acts only at effective level `autonomous`, caps attempts per (task, stage) via `PLANNER_RECOVERY_MAX_ATTEMPTS`, skips user-paused tasks, and excludes merge/PR/destructive actions (deferred to FN-7513) and comprehensive human-control safeguards (FN-7514).
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Planner oversight now requires confirmation before merge/PR actions and destructive/external side effects.
|
||||
category: feature
|
||||
dev: Adds `PlannerActionSideEffectClass` + `PlannerConfirmationRequest` and `classifyPlannerActionSideEffect`/`requiresPlannerConfirmation` (core), extends `decidePlannerRecovery` with an `await_confirmation` action, and adds `requestConfirmation`/`resolveConfirmation` gating to `PlannerRecoveryController` (engine). Merge/PR and destructive/external actions never execute without a recorded approval; bounded recovery (guidance/retry/targeted-fix) is unchanged. UX rendering, human-control safeguards, timeline, and run-audit land in follow-up tasks.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Planner overseer now stays fully hands-off for paused tasks and auto-merge-off / human-review tasks.
|
||||
category: feature
|
||||
dev: Adds the pure `evaluateOverseerHumanControl` policy (packages/engine/src/overseer-human-control-policy.ts), consulted at the top of `PlannerRecoveryController.tick()` before any action classification, confirmation gating, steering, retry, or dispatch — so a user-paused or `autoMerge:false`/human-review task never even records a pending confirmation. Reuses `allowsAutoMergeProcessing` from `@fusion/core` verbatim (never re-derives the auto-merge/human-review predicate). Distinguishes explicit user pause (`task.userPaused===true`, or `task.paused===true` with no `pausedReason`) from engine/self-healing parks (which always stamp a `pausedReason`). Emits a bounded `overseer:oversight-withheld-human-control` run-audit no-action event (metadata: `{ taskId, reason, stage, oversightLevel }`), deduped per (taskId, reason) so it does not spam every poll.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Configure planner oversight level per task and per project in the workflow editor and task create/detail.
|
||||
category: feature
|
||||
dev: Per-task `plannerOversightLevel` override exposed via TaskForm (Inherit/off/observe/steer/autonomous), threaded through createTask/updateTask; workflow-editor Values tab gets a first-class display entry. Workflow-native setting; not a project setting.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Add a configurable planner-overseer notification verbosity level (Silent/Errors/Important/All).
|
||||
category: feature
|
||||
dev: New workflow-native enum setting `plannerOversightNotificationLevel` in BUILTIN_OVERSIGHT_SETTINGS; default `important`. Resolves via resolveEffectiveSettings; emission gating that reads it lands in FN-7519/FN-7520.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Add a task-detail planner-overseer intervention timeline (stage, reason, action, outcome, attempts, links).
|
||||
category: feature
|
||||
dev: New core `PlannerInterventionEntry` model + `recordPlannerIntervention`/`getPlannerInterventionTimeline` helpers persisting via the run-audit store under the `overseer:intervention` mutation, plus a `PlannerInterventionTimeline` component rendered in the task-detail Planner Oversight cluster. Emission call-sites land in FN-7520.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Emit planner-overseer run-audit events for observations, steering, retries, recovery, confirmations, and escalations.
|
||||
category: feature
|
||||
dev: New core emitters (emitOverseerObservation/Steering/RecoveryAttempt/Retry/Confirmation/Escalation) in planner-overseer-events.ts, each mapping its decision-point to the correct intervention action/outcome and delegating to FN-7519's recordPlannerIntervention under the overseer:intervention mutation. Producer call-sites land in FN-7511/FN-7512/FN-7513.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Add an intelligent git-revert engine service and POST /api/tasks/:id/revert route.
|
||||
category: feature
|
||||
dev: New `packages/engine/src/task-revert.ts` exports `resolveTaskRevertCommits`, `classifyTaskRevert`, and `performTaskRevert` (squash/rebase/lineage attribution precedence, dry-run classification, guaranteed-clean rollback). Route enforces done/archived-only and autoMerge-off guard rails; conflicting results are returned unresolved for sibling FN-7524 (AI-undo) to act on. Workspace tasks return `unsupported`.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Auto-approve now reliably sends specified plans to the board without a manual approval stop.
|
||||
category: fix
|
||||
dev: FN-7526 — investigated the reported "plans still park at awaiting-approval when auto-approve is on" symptom; resolvePlanApprovalRequired, mergeEffectiveSettings/applyWorkflowSettingsOverlay, and every finalizeApprovedTask call site (specifyTask, recoverApprovedTask, retryUnavailablePlanReview, tryFinalizeExplicitDuplicateMarker) already honored project planApprovalMode: "auto-approve-all" over a stored workflow requirePlanApproval value — no production defect reproduced. Added end-to-end regression coverage across every enumerated surface (Plan Review reviewer-outage retry, refinement routing, self-healing starved-refinement recovery) using the real mergeEffectiveSettings pipeline instead of isolated bare-settings unit calls, plus explicit assertions that the independent release-authorization and Workflow Plan Review gates remain intact under auto-approve-all, so a future bare-settings call site is caught immediately instead of silently reintroducing the reported behavior.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix the in-dashboard Switch server menu not switching desktop local/remote.
|
||||
category: fix
|
||||
dev: The desktop shell's redirect effects in App.tsx read a dead `localServer` field that the preload never populates; extracted `resolveDesktopShellRedirectTarget` in appLifecycle.ts now derives the navigation target from the live `localRuntime`/`activeProfileId` state for both directions, and the unused `localServer` field was removed from `ShellConnectionState`.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Capture a structured performance snapshot when an agent task completes.
|
||||
category: feature
|
||||
dev: New AgentReflectionService.captureTaskPerformance persists a non-LLM post-task ReflectionMetrics record (duration, packages/files touched, verification command + scope, retry/rework count) and emits ids/counts-only `reflection:captured` run-audit telemetry; populates performanceSummary/latestReflection.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Task cards can now show the planner overseer's active state (idle/watching/steering/recovering/awaiting-confirmation).
|
||||
category: feature
|
||||
dev: Adds a serializable `PlannerOverseerRuntimeSnapshot` + pure `derivePlannerOverseerState` (core), a read-only `ProjectEngine.getPlannerOverseerRuntimeSnapshot(taskId)` accessor assembling it from the FN-7511 monitor + FN-7512/7513 recovery controller, and a best-effort additive `plannerOverseerState` enrichment on `GET /api/tasks` (mirrors the `branchProgress` pattern; never persisted, never fails the board load). Consumed by FN-7516's TaskCard.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix branch group completion checklists to show accurate landed/finished counts.
|
||||
category: fix
|
||||
dev: runAiMerge (the sole merge path since master-plan U0) never resolved branch-group routing or stamped mergeDetails.mergeTargetBranch/mergeTargetSource, so isBranchGroupMemberLanded permanently reported shared-group members as not landed. Routes through resolveBranchGroupMergeRouting (matching the legacy merger.ts pattern) and stamps the target fields on both the landed and no-op finalize paths; preserves merge-target-safety in isBranchGroupMemberLanded (a sibling/mismatched-branch member still never counts as landed).
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Branch groups no longer report complete (or become promotable) when an unlanded member is archived.
|
||||
category: fix
|
||||
dev: listTasksByBranchGroup membership now scans with includeArchived:true so an archived-but-unlanded member stays counted in total instead of silently dropping out; mergeDetails is now persisted on ArchivedTaskEntry so an archived member that had already landed keeps counting as landed. evaluateBranchGroupCompletion / promoteBranchGroup gate correctly; merge-target-safety in isBranchGroupMemberLanded is unchanged.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix the global GitLab integration setting not persisting when saved.
|
||||
category: fix
|
||||
dev: splitSettingsSave now diffs the five global GitLab keys (gitlabEnabled, gitlabInstanceUrl, gitlabApiBaseUrl, gitlabAuthToken, gitlabAuthTokenType) against scoped global initials only, never the project-effective merged initialValues, so a project override no longer suppresses a real global save.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix task-detail Activity view dropdown not opening reliably on mobile.
|
||||
category: fix
|
||||
dev: Guards the Activity menu's window resize/orientationchange/scroll close-listener with the same opening-tap timing guard already used for visualViewport, and exempts scroll events originating in the `.detail-tabs` scroller, so a same-gesture mobile tap echo (Android/iOS, fixed modal or `.floating-window--task-detail` popup) no longer closes the menu the instant it opens.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Manual "Run now" for the Database Backup automation now runs in-process like the scheduler, matching cron behavior.
|
||||
category: fix
|
||||
dev: The legacy single-command and command-step manual automation run path (`executeSingleCommand` in packages/dashboard/src/routes.ts) now intercepts `isInProcessBackupCommand`/`isInProcessMemoryBackupCommand` via the scoped TaskStore, mirroring `RoutineRunner.executeCommand`/`CronRunner`, instead of always shelling out via `exec()`. `formatInProcessBackupError`, `isInProcessBackupCommand`, and `isInProcessMemoryBackupCommand` are now exported from `@fusion/engine` for reuse. Existing onStep/onText live-run callbacks already stream incremental output for command/backup runs; added regression coverage confirming this holds for the new interception branch.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Task cards no longer show the "Auto-recovery" oversight badge unless oversight is explicitly configured.
|
||||
category: fix
|
||||
dev: `TaskCard.tsx`'s `showOversightBadge` gate now also suppresses the badge when the effective level equals `DEFAULT_PLANNER_OVERSIGHT_LEVEL` ("autonomous") and there is no explicit per-task `plannerOversightLevel` override; an explicit per-task override of "autonomous" still renders the badge.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Remove the per-card overseer-state ("Executor") badge from task cards.
|
||||
category: fix
|
||||
dev: Deleted the FN-7516 `card-overseer-state-badge` render, its card-local `deriveOverseerCardWatchedStage` helper/label maps, and its CSS; the sibling oversight-level badge (`card-oversight-badge`) is unaffected.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Original task prompt now renders as Markdown and is collapsed by default in the task Plan tab.
|
||||
category: feature
|
||||
dev: Task Detail Plan/Definition tab original-prompt section reuses the existing `.detail-source-toggle`/`.detail-source-chevron--expanded` collapse pattern and the shared `ReactMarkdown` pipeline (`remarkGfm`, `sharedRehypePlugins`, `markdownLinkifyComponents`); backed by read-only `task.description`, no change to the generated `PROMPT.md` editor/revision flow.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix agent-created artifacts not appearing live in the dashboard artifacts view.
|
||||
category: fix
|
||||
dev: Root cause was cross-instance artifact-registration replication, not the route/hook/render path (all already correct). `TaskStore.registerArtifact()` never bumped `lastModified`, and `checkForChanges()` (the polling replicator that lets a second TaskStore instance on the same project — e.g. the dashboard's cached store vs. the engine's own store — mirror events it did not write itself) only ever diffed the `tasks` table, never `artifacts`. A store instance that did not perform the write could therefore never observe or re-emit `artifact:registered`, leaving an already-open Documents/task Artifacts gallery stale until a full reload. Fixed by bumping `lastModified` on artifact writes and adding a strictly-increasing `rowid`-cursor poll over the `artifacts` table in `checkForChanges()`. See `packages/core/src/__tests__/artifacts.test.ts` and `packages/dashboard/src/routes/__tests__/artifacts-route-integration.test.ts` for regression coverage.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix the mobile terminal shortcut bar so it scrolls horizontally to reach every key.
|
||||
category: fix
|
||||
dev: Added `min-width: 0` to `.terminal-shortcut-panel` to defeat the flex min-width:auto trap that clipped overflow instead of engaging `overflow-x: auto`.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Planner-oversight intervention timeline now populates from real engine activity.
|
||||
category: fix
|
||||
dev: Wires PlannerOverseerMonitor/PlannerRecoveryController decision points to the FN-7520 emitOverseer* façade with the real TaskStore; observation/escalation emission deduped per (task, stage[, signal]).
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Show the "Global" prefix on the Authentication entry in the mobile Settings picker.
|
||||
category: fix
|
||||
dev: resolveSettingsSectionOptionLabel now derives the Global-group prefix for storage-less (scope: undefined) sections in SettingsModal.tsx (FN-7552).
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Add a dedicated Keyboard Shortcuts settings section with click-to-record capture and more configurable actions.
|
||||
category: feature
|
||||
dev: Relocates dashboardKeyboardShortcuts into its own settings section, adds a ShortcutCaptureInput recorder, and extends DashboardShortcutAction with openFiles/openSettings/openCommandCenter/newTask actions wired into existing App nav handlers.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Plan auto-approval is now the default; specified tasks skip manual approval unless you opt into workflow/require-all.
|
||||
category: feature
|
||||
dev: `DEFAULT_PROJECT_SETTINGS.planApprovalMode` flips `workflow` → `auto-approve-all`; existing projects with an explicit stored value are unchanged; consumed by `resolvePlanApprovalRequired` at the triage gating sites.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Add a "Coding (Ideas)" workflow with a manual Ideas intake and a merged Todo planner column.
|
||||
category: feature
|
||||
dev: New `builtin:coding-ideas` clones the default stepwise pipeline with an `ideas` intake (autoTriage:false) in front of a merged `todo` planner+capacity column. createTask lands cards in the workflow's intake column; the triage service plans unplanned todo tasks in place; the scheduler skips bootstrap-prompt todo tasks; TaskCard gains a Start button and a Ready badge.
|
||||
7
.changeset/proud-horses-chat.md
Normal file
7
.changeset/proud-horses-chat.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Preserve Hermes chat session state and project runtime routing more reliably.
|
||||
category: fix
|
||||
dev: Refreshes cached project chat plugin runners and hardens Hermes CLI session/error handling.
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -36,19 +36,19 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: bun-linux-x64
|
||||
binary: fn-linux-x64
|
||||
binary: fn-cli-linux-x64
|
||||
- os: ubuntu-24.04-arm
|
||||
target: bun-linux-arm64
|
||||
binary: fn-linux-arm64
|
||||
binary: fn-cli-linux-arm64
|
||||
- os: macos-latest
|
||||
target: bun-darwin-arm64
|
||||
binary: fn-darwin-arm64
|
||||
binary: fn-cli-darwin-arm64
|
||||
# bun-darwin-x64 (Intel) dropped: macos-13 runners are scarce and
|
||||
# blocked releases by sitting queued for hours. The CLI ships
|
||||
# Apple-Silicon-only for macOS; desktop macOS DMG/ZIP is universal.
|
||||
- os: windows-latest
|
||||
target: bun-windows-x64
|
||||
binary: fn-windows-x64.exe
|
||||
binary: fn-cli-windows-x64.exe
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
8
.github/workflows/test-release.yml
vendored
8
.github/workflows/test-release.yml
vendored
@@ -19,18 +19,18 @@ jobs:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: bun-linux-x64
|
||||
binary: fn-linux-x64
|
||||
binary: fn-cli-linux-x64
|
||||
- os: ubuntu-24.04-arm
|
||||
target: bun-linux-arm64
|
||||
binary: fn-linux-arm64
|
||||
binary: fn-cli-linux-arm64
|
||||
- os: macos-latest
|
||||
target: bun-darwin-arm64
|
||||
binary: fn-darwin-arm64
|
||||
binary: fn-cli-darwin-arm64
|
||||
# bun-darwin-x64 (Intel) dropped: macos-13 runner scarcity — CLI is
|
||||
# Apple-Silicon-only for macOS. Keep in sync with release.yml.
|
||||
- os: windows-latest
|
||||
target: bun-windows-x64
|
||||
binary: fn-windows-x64.exe
|
||||
binary: fn-cli-windows-x64.exe
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
801
CHANGELOG.md
801
CHANGELOG.md
@@ -2,60 +2,727 @@
|
||||
|
||||
User-facing release notes aggregated across all packages. This file is auto-synced from each `packages/*/CHANGELOG.md` by `scripts/release.mjs` — do not edit by hand.
|
||||
|
||||
## 0.55.0
|
||||
|
||||
### New
|
||||
|
||||
- Allow configuring permissions for ephemeral and permanent agents.
|
||||
- Add a Settings override for the local Cursor CLI binary path.
|
||||
- Add GitLab instance URL settings for GitLab.com and self-managed servers.
|
||||
- Add GitLab access-token settings for personal, project, and group tokens.
|
||||
- Add GitLab project issue, group issue, and merge request imports.
|
||||
- Display linked GitLab tracking metadata and stale badges on tasks.
|
||||
- Add GitLab comment and auto-close lifecycle actions for linked work items.
|
||||
- Add GitLab as a Command Center Signals connector.
|
||||
- Add explicit onboarding choices to use, initialize, or clone a git repository.
|
||||
- Let chat update existing agents without delete/recreate.
|
||||
- Add sidebar rename buttons to direct Chat conversations.
|
||||
- Add a bundled Linear import plugin for creating tasks from Linear issues.
|
||||
- Add a mandatory Planning Mode deepening checkpoint before final summaries.
|
||||
- Add visible create buttons and recursive search to Project Files.
|
||||
- Collapse mobile Chat thread controls into one compact header row.
|
||||
- Add a GitLab enable toggle and collapsible Settings controls.
|
||||
## 0.56.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Stop recurring Windows Terminal warning popups during terminal startup.
|
||||
- Fix dashboard localStorage quota exhaustion from stale SWR caches and add a Clear local data escape hatch.
|
||||
- Fix `fusion desktop` on Windows and published npm installs (Electron dependency, GPU/sandbox flags, dashboard reuse).
|
||||
- First-run agent setup no longer errors on a duplicate CEO; desktop Switch-server button now opens the connection menu.
|
||||
- Allow operators to delete archived tasks.
|
||||
- Show workflow template block boundary connectors in the graph editor.
|
||||
- Preserve the selected dashboard project across browser refreshes.
|
||||
- Detect Cursor CLI installations that expose Windows cmd or bat shims.
|
||||
- Preserve GitLab tracking metadata for CLI and extension imports.
|
||||
- Keep Planning Mode Refine Further from getting stuck on duplicate generation.
|
||||
- Show task status badges on Documents task groups.
|
||||
- Suppress misleading Anthropic Subscription re-login banners when another Anthropic auth method is active.
|
||||
- Keep Anthropic authentication cards grouped near the top in Settings.
|
||||
- Stop planner model fallback loops with a clear terminal triage error.
|
||||
- Recover stale task branch-group references from Task Detail after server restarts.
|
||||
- Return GitHub issue import actions to the main issue list.
|
||||
- Prevent Planning Mode sessions from failing when MCP resolution returns no shaped result.
|
||||
- Fix Android mobile terminal spacing while the keyboard is open.
|
||||
- Fix Last 30 days token usage to include every model in Command Center.
|
||||
- Include supported chat interactions in Command Center token usage totals.
|
||||
- Preserve workflow setting edits made while a values save is still in flight.
|
||||
- Preserve migrated workflow settings when project identity is assigned later.
|
||||
- Show the bundled Linear Import plugin in Plugin Manager and dashboard plugin surfaces.
|
||||
- Fix the mobile Chat header so back navigation and session selection stay on one row.
|
||||
- Fix iOS mobile terminal spacing when opening terminals with the keyboard already visible.
|
||||
- Default fresh startup and theme reset to System mode.
|
||||
- Remember task popup size and position when switching between tasks.
|
||||
- Fix iPhone Safari terminal text spacing with the keyboard open.
|
||||
- Count planning tasks correctly in the dashboard footer queue metric.
|
||||
- Show conversation titles in the mobile Chat dropdown.
|
||||
- Merges no longer fail when a task adds a dependency without updating the lockfile.
|
||||
- Fix Anthropic subscription showing "logged in" while all model calls fail.
|
||||
- Fix "Invalid transition" error when moving cards out of a custom workflow column like Coding (Ideas) → Ideas.
|
||||
- Fix overlapping Record and Clear buttons in the Keyboard Shortcuts settings rows on desktop and mobile.
|
||||
- Fix persistent mobile terminal inter-character spacing (5th recurrence root cause).
|
||||
- Fix manual PR actions hidden when a task auto-merge override was on but global auto-merge was off.
|
||||
|
||||
## 0.56.0
|
||||
|
||||
### @fusion/dashboard
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- @fusion/core@0.56.0
|
||||
- @fusion/engine@0.56.0
|
||||
- @fusion/i18n@0.39.20
|
||||
- @fusion-plugin-examples/cli-printing-press@0.1.37
|
||||
- @fusion-plugin-examples/compound-engineering@0.1.20
|
||||
- @fusion-plugin-examples/dependency-graph@0.1.51
|
||||
- @fusion-plugin-examples/roadmap@0.1.39
|
||||
- @fusion-plugin-examples/cursor-runtime@0.1.39
|
||||
- @fusion-plugin-examples/droid-runtime@0.1.46
|
||||
- @fusion-plugin-examples/hermes-runtime@0.2.70
|
||||
- @fusion-plugin-examples/openclaw-runtime@0.2.70
|
||||
- @fusion-plugin-examples/paperclip-runtime@0.2.70
|
||||
|
||||
### @fusion/desktop
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- @fusion/core@0.56.0
|
||||
- @fusion/dashboard@0.56.0
|
||||
- @fusion/engine@0.56.0
|
||||
|
||||
### @fusion/engine
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- @fusion/core@0.56.0
|
||||
- @fusion/pi-claude-cli@0.56.0
|
||||
|
||||
### @fusion/plugin-sdk
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- @fusion/core@0.56.0
|
||||
|
||||
### @runfusion/fusion
|
||||
|
||||
#### Minor Changes
|
||||
|
||||
- d16c8b4: summary: Expand first-run AI provider quick-start choices beyond Anthropic.
|
||||
category: feature
|
||||
dev: Moves advanced/all-provider onboarding controls under the quick-start provider section.
|
||||
- 315f3bc: summary: Show Git prerequisite guidance during first-run GitHub onboarding.
|
||||
category: feature
|
||||
dev: Adds bounded server-host git availability to auth status and onboarding.
|
||||
- 50cdab1: summary: Add GitHub OAuth and CLI setup actions to first-run onboarding.
|
||||
category: feature
|
||||
dev: GitHub onboarding now shows in-flow OAuth connect, gh auth login, and gh install guidance.
|
||||
- 2f23d22: summary: Add configurable dashboard keyboard shortcuts for Quick Chat and Terminal.
|
||||
category: feature
|
||||
dev: Global dashboardKeyboardShortcuts settings, guarded document-level key handling, and Escape topmost-popup dismissal.
|
||||
- efa8105: summary: Add search in Settings so operators can find settings faster.
|
||||
category: feature
|
||||
dev: Dashboard Settings filters visible sections by setting labels and keywords.
|
||||
- 7d8a1b8: summary: Add a pinned below-application layout option for the dashboard terminal.
|
||||
category: feature
|
||||
dev: Terminal display mode now supports persisted docked, floating, and below layouts, with header controls replacing the footer shell.
|
||||
- 87a700c: summary: Add a Reset Settings button to restore a menu's or all project settings to defaults.
|
||||
category: feature
|
||||
dev: New tested section→keys (scope-aware) registry (packages/dashboard/app/components/settings/section-keys.ts) drives per-menu reset via updateSettings/updateGlobalSettings with null-as-delete; non-blob sections (secrets, MCP, plugins, memory, auth, prompts, CLI agents, runtimes) are excluded with a documented reason.
|
||||
- 68f5153: summary: Add a per-workflow planner oversight level setting (Off, Observe, Steer, Autonomous recovery).
|
||||
category: feature
|
||||
dev: New workflow setting `plannerOversightLevel` declared in BUILTIN_OVERSIGHT_SETTINGS; default `autonomous`. Per-task override and engine behavior land in follow-up tasks.
|
||||
- aa757bc: summary: Tasks can override the workflow planner oversight level (Off, Observe, Steer, Autonomous recovery).
|
||||
category: feature
|
||||
dev: New nullable Task.plannerOversightLevel field (migration 137, SCHEMA_VERSION 137) mirroring executionMode; NULL inherits the workflow setting. Adds resolveEffectivePlannerOversightLevel precedence helper. Dashboard UI/API threading and engine behavior land in follow-up tasks.
|
||||
- 0689250: summary: Planner oversight now defaults to full steering/control for every workflow unless explicitly changed.
|
||||
category: feature
|
||||
dev: Confirms the `plannerOversightLevel` workflow-setting default is the highest (autonomous) level; unset workflow value and unset per-task override both resolve to full steering via `resolveEffectivePlannerOversightLevel` (task override → workflow effective value → autonomous), adding dedicated regression coverage for the "unless explicitly disabled" precedence.
|
||||
- 12a6d1b: summary: Planner oversight now monitors tasks across executor, reviewer, merger, pull-request, and workflow-gate stages.
|
||||
category: feature
|
||||
dev: Adds records-only PlannerOverseerMonitor + resolveWatchedStage + OverseerStageObservation in @fusion/engine, gated by resolveEffectivePlannerOversightLevel (off = no observation) and wired into ProjectEngine via a bounded poll. Steering/recovery and UI land in FN-7512/FN-7515+.
|
||||
- 81f2053: summary: Planner oversight can autonomously inject guidance, retry stuck/failed steps, and request fixes within bounded limits.
|
||||
category: feature
|
||||
dev: Adds pure `decidePlannerRecovery` + recovery types (core) and `PlannerRecoveryController` with injected guidance/retry/targeted-fix handlers (engine), consuming the FN-7511 observation. Acts only at effective level `autonomous`, caps attempts per (task, stage) via `PLANNER_RECOVERY_MAX_ATTEMPTS`, skips user-paused tasks, and excludes merge/PR/destructive actions (deferred to FN-7513) and comprehensive human-control safeguards (FN-7514).
|
||||
- 2cc84b5: summary: Planner oversight now requires confirmation before merge/PR actions and destructive/external side effects.
|
||||
category: feature
|
||||
dev: Adds `PlannerActionSideEffectClass` + `PlannerConfirmationRequest` and `classifyPlannerActionSideEffect`/`requiresPlannerConfirmation` (core), extends `decidePlannerRecovery` with an `await_confirmation` action, and adds `requestConfirmation`/`resolveConfirmation` gating to `PlannerRecoveryController` (engine). Merge/PR and destructive/external actions never execute without a recorded approval; bounded recovery (guidance/retry/targeted-fix) is unchanged. UX rendering, human-control safeguards, timeline, and run-audit land in follow-up tasks.
|
||||
- 79ab367: summary: Planner overseer now stays fully hands-off for paused tasks and auto-merge-off / human-review tasks.
|
||||
category: feature
|
||||
dev: Adds the pure `evaluateOverseerHumanControl` policy (packages/engine/src/overseer-human-control-policy.ts), consulted at the top of `PlannerRecoveryController.tick()` before any action classification, confirmation gating, steering, retry, or dispatch — so a user-paused or `autoMerge:false`/human-review task never even records a pending confirmation. Reuses `allowsAutoMergeProcessing` from `@fusion/core` verbatim (never re-derives the auto-merge/human-review predicate). Distinguishes explicit user pause (`task.userPaused===true`, or `task.paused===true` with no `pausedReason`) from engine/self-healing parks (which always stamp a `pausedReason`). Emits a bounded `overseer:oversight-withheld-human-control` run-audit no-action event (metadata: `{ taskId, reason, stage, oversightLevel }`), deduped per (taskId, reason) so it does not spam every poll.
|
||||
- c16cc9e: summary: Configure planner oversight level per task and per project in the workflow editor and task create/detail.
|
||||
category: feature
|
||||
dev: Per-task `plannerOversightLevel` override exposed via TaskForm (Inherit/off/observe/steer/autonomous), threaded through createTask/updateTask; workflow-editor Values tab gets a first-class display entry. Workflow-native setting; not a project setting.
|
||||
- aae603b: summary: Add a configurable planner-overseer notification verbosity level (Silent/Errors/Important/All).
|
||||
category: feature
|
||||
dev: New workflow-native enum setting `plannerOversightNotificationLevel` in BUILTIN_OVERSIGHT_SETTINGS; default `important`. Resolves via resolveEffectiveSettings; emission gating that reads it lands in FN-7519/FN-7520.
|
||||
- d10ea9a: summary: Add a task-detail planner-overseer intervention timeline (stage, reason, action, outcome, attempts, links).
|
||||
category: feature
|
||||
dev: New core `PlannerInterventionEntry` model + `recordPlannerIntervention`/`getPlannerInterventionTimeline` helpers persisting via the run-audit store under the `overseer:intervention` mutation, plus a `PlannerInterventionTimeline` component rendered in the task-detail Planner Oversight cluster. Emission call-sites land in FN-7520.
|
||||
- bf68839: summary: Emit planner-overseer run-audit events for observations, steering, retries, recovery, confirmations, and escalations.
|
||||
category: feature
|
||||
dev: New core emitters (emitOverseerObservation/Steering/RecoveryAttempt/Retry/Confirmation/Escalation) in planner-overseer-events.ts, each mapping its decision-point to the correct intervention action/outcome and delegating to FN-7519's recordPlannerIntervention under the overseer:intervention mutation. Producer call-sites land in FN-7511/FN-7512/FN-7513.
|
||||
- c4d81fe: summary: Add an AI-undo fallback task when reverting a done task via git conflicts or is unsupported.
|
||||
category: feature
|
||||
dev: `POST /api/tasks/:id/revert` now accepts `{ mode?: "git" | "ai" | "auto" }` (default `"auto"`). `"auto"` tries the FN-7523 git-revert path first and falls back to creating an AI-undo board task (`{ mode: "ai", createdTaskId, alreadyOpen? }`) on a conflicting or unsupported (e.g. workspace) git result; `needsHuman` (autoMerge-off) never triggers the fallback. `"ai"` always creates the AI-undo task; `"git"` keeps the FN-7523 git-only contract, which is otherwise unchanged. New engine exports: `createAiUndoTask`, `buildAiUndoTaskDescription`, `REVERT_OF_METADATA_KEY`. New core store method `TaskStore.findOpenRevertTaskForSource` backs the idempotency guard (an open undo task suppresses a duplicate; a closed one does not).
|
||||
- e7cb2f1: summary: Add a Revert action to Done/Archived task cards to undo landed changes.
|
||||
category: feature
|
||||
dev: Wires onRevertTask through Board/List/Detail surfaces; calls POST /tasks/:id/revert in "auto" mode with a conflict-confirm AI-undo fallback (mode: "ai").
|
||||
- 5ad8ec8: summary: Capture a structured performance snapshot when an agent task completes.
|
||||
category: feature
|
||||
dev: New AgentReflectionService.captureTaskPerformance persists a non-LLM post-task ReflectionMetrics record (duration, packages/files touched, verification command + scope, retry/rework count) and emits ids/counts-only `reflection:captured` run-audit telemetry; populates performanceSummary/latestReflection.
|
||||
- 726cbf8: summary: Task cards can now show the planner overseer's active state (idle/watching/steering/recovering/awaiting-confirmation).
|
||||
category: feature
|
||||
dev: Adds a serializable `PlannerOverseerRuntimeSnapshot` + pure `derivePlannerOverseerState` (core), a read-only `ProjectEngine.getPlannerOverseerRuntimeSnapshot(taskId)` accessor assembling it from the FN-7511 monitor + FN-7512/7513 recovery controller, and a best-effort additive `plannerOverseerState` enrichment on `GET /api/tasks` (mirrors the `branchProgress` pattern; never persisted, never fails the board load). Consumed by FN-7516's TaskCard.
|
||||
- 2ed06f9: summary: Support reverting multi-repo workspace tasks via git, all-or-nothing across sub-repos.
|
||||
category: feature
|
||||
dev: Extends `packages/engine/src/task-revert.ts` with `resolveWorkspaceTaskRevertCommits`/`revertWorkspaceTask` and wires `POST /api/tasks/:id/revert` to dispatch workspace tasks (`isWorkspaceTask`) to the new path; returns `{ mode: "git", clean, workspace: { repos: [...] }, conflicts? }`. Single-repo `performTaskRevert` path is unchanged.
|
||||
- 8c6f76c: summary: Add per-sha revert commit granularity to the task revert API and service.
|
||||
category: feature
|
||||
dev: `performTaskRevert` and `POST /api/tasks/:id/revert` accept an optional `granularity: "squash" | "per-sha"` (default `"squash"`, unchanged FN-7523 behavior). `"per-sha"` creates one attributed `revert(FN-xxxx)` commit per original sha (each with its own `Fusion-Task-Id` trailer and audit line), skipping no-op shas without empty commits. A mid-batch conflict in either mode rolls back the whole batch to the pre-call HEAD — no partially-landed per-sha commits. The clean result now reports `revertCommitShas: string[]` (all created commits) alongside the existing `revertCommitSha` (kept for backward compatibility).
|
||||
- f992e6a: summary: Add a dedicated Keyboard Shortcuts settings section with click-to-record capture and more configurable actions.
|
||||
category: feature
|
||||
dev: Relocates dashboardKeyboardShortcuts into its own settings section, adds a ShortcutCaptureInput recorder, and extends DashboardShortcutAction with openFiles/openSettings/openCommandCenter/newTask actions wired into existing App nav handlers.
|
||||
- 2df6c35: summary: Open a revert PR for done/archived tasks when autoMerge is disabled instead of refusing.
|
||||
category: feature
|
||||
dev: `POST /api/tasks/:id/revert` gains an additive `{ mode: "pr", clean: true, prUrl, prNumber, revertBranch, existingPr? }` result for clean single-repo reverts under `autoMerge:false`, reusing `GitHubClient.createPr`, `findPrForBranch` idempotency, and the `manual:true` PR handoff. New engine export `prepareRevertPrBranch` (packages/engine/src/task-revert.ts) prepares the dedicated `fusion/revert-<id>` branch without ever mutating the base branch. Existing `{ mode: "git" | "ai", ... }` shapes and the `autoMerge:true` path are unchanged.
|
||||
- 94e9d15: summary: AI-undo tasks now default to a configurable, stricter review workflow.
|
||||
category: feature
|
||||
dev: New project setting `aiUndoTaskWorkflowId` (default `builtin:review-heavy`) selects the workflow for AI-undo board tasks created by `POST /api/tasks/:id/revert` (`mode: "ai"`, the `auto` conflict fallback, and the workspace conflict fallback all share the `createAiUndoResult()` closure, so all three inherit this default). A blank/unset value means the created task inherits the project default workflow (pre-FN-7556 behavior). The route validates the configured id via `getWorkflowDefinition`/`isBuiltinWorkflowId` and falls back to inherit (with a logged warning) on a blank or unknown value, so a misconfigured id never breaks AI-undo task creation. The engine's `createAiUndoTask` helper stays pure — it only forwards a `workflowId` it is given, never resolves the setting itself. The Settings Modal UI field for this setting is a deliberate follow-up task; the value is settable today only via the settings API.
|
||||
- 3dd227b: summary: Plan auto-approval is now the default; specified tasks skip manual approval unless you opt into workflow/require-all.
|
||||
category: feature
|
||||
dev: `DEFAULT_PROJECT_SETTINGS.planApprovalMode` flips `workflow` → `auto-approve-all`; existing projects with an explicit stored value are unchanged; consumed by `resolvePlanApprovalRequired` at the triage gating sites.
|
||||
- 78d4db9: summary: Fusion self-repo issue-close comments now show current and target release versions.
|
||||
category: feature
|
||||
dev: GitHubIssueCommentService appends "Current version: v{current}" and "Target release: v{next-minor}" lines when the linked source issue is runfusion/fusion; other repos unchanged. Version resolved via getCliPackageVersion.
|
||||
- 7435849: summary: Open one revert PR per sub-repo for workspace tasks when autoMerge is disabled.
|
||||
category: feature
|
||||
dev: `POST /api/tasks/:id/revert` gains an additive workspace `{ mode: "pr", clean: true, workspace: { repos: [{ repo, revertBranch, prUrl, prNumber, existingPr? }] } }` result for clean multi-repo reverts under `autoMerge:false`, extending FN-7554's single-repo `mode:"pr"` path. New engine export `prepareWorkspaceRevertPrBranches` (packages/engine/src/task-revert.ts) classifies every sub-repo first and only prepares a dedicated `fusion/revert-<id>` branch per sub-repo when all are clean/already-reverted (all-or-nothing at the branch-prep phase), never force-writing any sub-repo integration branch. The route resolves owner/repo and checks the rate limiter for every sub-repo before pushing/creating any PR, so GitHub-unconfigured/rate-limited cases degrade the whole task to `needsHuman` rather than opening a partial subset of PRs. Existing `{ mode: "git" | "ai" | "pr", ... }` shapes, the `autoMerge:true` workspace path, and FN-7554's single-repo path are unchanged.
|
||||
- 73b38ba: summary: Add a Settings → General picker to choose the workflow used for AI-undo (revert) tasks.
|
||||
category: feature
|
||||
dev: Surfaces `aiUndoTaskWorkflowId` (default `builtin:review-heavy`) in GeneralSection; empty selection means "inherit project default workflow", matching the revert route's blank-is-inherit behavior from FN-7556.
|
||||
- 42bbe58: summary: Add "Ask user question" and "Exit gate" workflow nodes for mid-flow chat reach-out and early exit.
|
||||
category: feature
|
||||
dev: New IR node kinds `ask-user` (reuses await-input park/resume; surfaces the question in the task chat) and `exit-gate` (terminates the workflow early, optional condition). Editor palette + summaries + help updated; `prompt`+`awaitInput` remains a back-compat alias.
|
||||
- 53fe0d7: summary: Add a built-in "Brainstorming" workflow that talks to you before planning.
|
||||
category: feature
|
||||
dev: Registers `builtin:brainstorming` (non-default, default-enabled) composing FN-7579's `ask-user` → refine → `exit-gate`-on-approval phase ahead of the normal coding plan/execute/review/merge spine. Parity suite (`builtin-workflows.test.ts`) extended for the new entry.
|
||||
- ecbbb29: summary: Add a "Coding (Ideas)" workflow with a manual Ideas intake and a merged Todo planner column.
|
||||
category: feature
|
||||
dev: New `builtin:coding-ideas` clones the default stepwise pipeline with an `ideas` intake (autoTriage:false) in front of a merged `todo` planner+capacity column. createTask lands cards in the workflow's intake column; the triage service plans unplanned todo tasks in place; the scheduler skips bootstrap-prompt todo tasks; TaskCard gains a Start button and a Ready badge.
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- 8668a05: summary: Add a workflow setting to disable automatic large-task triage splitting.
|
||||
category: feature
|
||||
dev: Adds triageProactiveSubtaskSplittingEnabled while preserving explicit breakIntoSubtasks requests.
|
||||
- 978cdda: summary: Show active Plan Review progress on triage task cards.
|
||||
category: fix
|
||||
dev: TaskCard now renders the existing progress affordance for Triage only when unified progress has active workflow work.
|
||||
- 635fca2: summary: Remove the eye icon markdown/plain toggle from chat; messages always render as Markdown.
|
||||
category: breaking
|
||||
dev: Removed ChatView `chat-thread-header-render-toggle` (desktop + mobile), `showAllAsPlain` state, and `chat.showRenderedMarkdown`/`chat.showPlainText` i18n keys (FN-7541).
|
||||
- 3d55102: summary: Clarify task-detail oversight Nudge/Explain controls: visible label, disabled reason, always-openable Explain panel.
|
||||
category: fix
|
||||
dev: TaskDetailModal now renders a `detail-oversight-controls-label` group label and `detail-overseer-nudge-disabled-reason` helper text (both gated by the existing oversight-cluster visibility condition); Explain no longer disables on `!canExplainOverseer` since it is read-only. Nudge's `canNudgeOverseer` gate and Stop's confirm dialog are unchanged.
|
||||
- 0f1cd0a: summary: Unify border, radius, and height of the task-detail Priority/Execution/Oversight controls.
|
||||
category: fix
|
||||
dev: Adds a shared --detail-control-border-radius token alongside --detail-priority-control-min-height so .detail-priority-chip, .detail-execution-mode-toggle, .detail-oversight-chip, and .detail-oversight-menu-trigger all resolve the same border-width/color/radius/height.
|
||||
- b42ba9f: summary: Keep the task-detail Activity view menu open during mobile iOS taps.
|
||||
category: fix
|
||||
dev: Guards the Activity views dropdown against iOS visualViewport resize/scroll echoes during menu opening.
|
||||
- a7559b0: summary: Fix Anthropic subscription login when pasted callback URLs contain fragment OAuth parameters.
|
||||
category: fix
|
||||
dev: Normalizes pasted OAuth callback fragments before resolving dashboard manual-code login prompts.
|
||||
- 4b530a6: summary: Fix Claude/Anthropic subscription re-login showing "Login did not complete" after logging out.
|
||||
category: fix
|
||||
dev: Anthropic subscription OAuth is aliased across the legacy `anthropic` row (where interactive login persists the credential) and the `anthropic-subscription` id (where the settings card's in-memory logged-out suppression and status read are keyed). Re-login wrote only `anthropic`, so `loggedOutProviders` kept suppressing `anthropic-subscription` and the card reported failure despite a valid stored credential until process restart. auth-storage's proxy now clears the logged-out state on both aliases when either is re-authenticated (new `login` trap + hardened `set` trap via `clearReauthenticatedLogoutState`; raw api_key writes stay scoped to their own card). Also surfaces background OAuth login failures on `GET /auth/status` (`loginError`) + server logs so future paste-callback failures are diagnosable instead of a generic error.
|
||||
- a5ac3c3: summary: Stop self-healing from killing actively-running tasks after ~30 minutes.
|
||||
category: fix
|
||||
dev: FN-7566. isPhantomExecutorBinding's liveness gate (heartbeat/checkout/runAudit) was blind to ephemeral executor agents, leaving only the age>graceMs\*3 threshold, so any ephemeral-executor task running longer than ~30 min was reclaimed to `todo` mid-flight. Adds the in-process live-session veto (activeSessionRegistry path / executingTaskLock / isTaskActive), mirroring the isWorkspaceTaskLive/sessionDead predicate, and honors clearPhantomExecutorBinding's live-session refusal in reclaimSelfOwnedBranchConflicts.
|
||||
- 8912399: summary: Stop Windows Terminal version dialogs from popping up when opening the dashboard or Settings on Windows.
|
||||
category: fix
|
||||
dev: Root cause was the worktrunk integration, not the embedded terminal: worktrunk's CLI is named `wt`, which collides with Windows Terminal (`wt.exe`) on PATH, so probing it with `wt --version` launched Windows Terminal. Fixed by (1) `useWorktrunkInstallStatus` only auto-fetching `/api/worktrunk/status` when the integration is enabled (user opt-in) instead of on every Settings/dashboard mount, and (2) an engine-level guard in `probeWorktrunk` that refuses to exec a resolved `wt` that is the Windows Terminal alias (under `WindowsApps` / a `WindowsTerminal` package dir), covering all resolution surfaces.
|
||||
- b800f7d: summary: Select newly created folders automatically during project setup.
|
||||
category: fix
|
||||
dev: Adds DirectoryPicker opt-in selection for project-registration surfaces while preserving default picker behavior.
|
||||
- 9dc248e: summary: Prevent Desktop update banners from using 0.0.0 as the current version.
|
||||
category: fix
|
||||
dev: Dashboard update checks now resolve packaged @fusion/desktop metadata and fail closed for unresolved versions.
|
||||
- 52dbc0e: summary: Quit Fusion Desktop on Windows when the window is closed.
|
||||
category: fix
|
||||
dev: Updates Electron close lifecycle so Windows shutdown reaches embedded runtime cleanup.
|
||||
- ced783e: summary: Open desktop Anthropic Subscription OAuth logins in the system browser.
|
||||
category: fix
|
||||
dev: Adds Electron window-open policy coverage and preserves Settings auth polling completion paths.
|
||||
- 50786f2: summary: Delay GitHub setup warnings for one day and add a dashboard connect action.
|
||||
category: fix
|
||||
dev: Dashboard setup warnings now gate GitHub prompts per project and route the CTA to Settings → Authentication.
|
||||
- b4b1f6d: summary: Fix a false AI engine not running banner in desktop mode.
|
||||
category: fix
|
||||
dev: Distinguishes transient embedded desktop engine startup from true dashboard-only mode.
|
||||
- e8b7362: summary: Clarify the desktop Connection Manager add-remote flow.
|
||||
category: fix
|
||||
dev: Desktop Connection Manager now separates Local Server context from saved remote profiles and collapses the remote editor until add/edit.
|
||||
- 0900a38: summary: Restore Local Server in the desktop Switch server list.
|
||||
category: fix
|
||||
dev: Desktop Connection Manager now lists local and saved remote destinations together.
|
||||
- a2b09f2: summary: Make right-dock task list clicks respect the task popup setting.
|
||||
category: fix
|
||||
dev: Threads openMobileTasksInPopup through the right-dock Tasks list route while preserving embedded dock detail when disabled.
|
||||
- 0f05156: summary: Auto-retry retryable Code Review remediation failures.
|
||||
category: fix
|
||||
dev: Prevents retryable code-review-remediation graph failures from stranding tasks in in-review.
|
||||
- 20184ac: summary: Fix no-op task branch recovery after a previously landed task.
|
||||
category: fix
|
||||
dev: Merge/recovery ownership classification now checks no-diff branches before foreign trailer rejection.
|
||||
- 82493e0: summary: Allow documented source-free task-artifact deliveries to finish without commits.
|
||||
category: fix
|
||||
dev: fn_task_done now recognizes explicit gitignored .fusion/tasks artifact contracts while preserving source-change no-commit guards.
|
||||
- 5689346: summary: Fix direct merges so Push to remote after merge honors the configured remote and branch.
|
||||
category: fix
|
||||
dev: Resolves remote-only push targets from the merge integration branch and preserves non-fatal push errors on done tasks.
|
||||
- b42be87: summary: Keep task popups on the board layer with Activity menus above them.
|
||||
category: fix
|
||||
dev: Task-detail FloatingWindow callers use a lower layer band, and Activity view menus reposition after popup geometry changes.
|
||||
- 61c8bdc: summary: Keep accepted chat requests waiting instead of showing false first-event timeout failures.
|
||||
category: fix
|
||||
dev: Dashboard chat POST streams no longer abort accepted-but-silent responses on the client first-event timer.
|
||||
- e8dc2ae: summary: Show each task's original prompt in the Plan tab alongside the generated plan.
|
||||
category: fix
|
||||
dev: Adds a read-only Task Detail original-prompt section backed by task.description.
|
||||
- d2e3134: summary: Add before-to-after transformation summaries to generated task definitions.
|
||||
category: feature
|
||||
dev: Built-in standard and fast triage prompts now require a `## Before → After Transformation` section.
|
||||
- b0208c1: summary: Restore terminal Ctrl/Cmd copy and paste shortcuts.
|
||||
category: fix
|
||||
dev: Integrated and embedded terminals now own physical clipboard paste to avoid swallowed or duplicate input.
|
||||
- 2797803: summary: Show first-token and tool processing durations in task agent logs.
|
||||
category: feature
|
||||
dev: Adds optional agent-log timing fields `timeToFirstTokenMs` and `durationMs`.
|
||||
- a2d6349: summary: Fix mobile Chat composer being hidden behind the keyboard accessory bar.
|
||||
category: fix
|
||||
dev: Adds keyboard-open bottom clearance in ChatView so the composer clears the iOS input-assistant/autofill bar without a persistent .chat-thread transform or Android reserved-gap.
|
||||
- 4baa4c4: summary: Settings descriptions now show each setting's default value.
|
||||
category: feature
|
||||
dev: Appended default-value copy to settings.\* i18n descriptions across Global, Runtimes, and Project Settings sections, sourced from DEFAULT_GLOBAL_SETTINGS/DEFAULT_PROJECT_SETTINGS in settings-schema.ts; added settings-default-descriptions.test.tsx guarding that every surfaced setting states a default (or explicit "inherits"/"no default \u2014 unset") and that every DEFAULT_SETTINGS key is documented or allowlisted as not surfaced.
|
||||
- 53d7b7e: summary: Add an intelligent git-revert engine service and POST /api/tasks/:id/revert route.
|
||||
category: feature
|
||||
dev: New `packages/engine/src/task-revert.ts` exports `resolveTaskRevertCommits`, `classifyTaskRevert`, and `performTaskRevert` (squash/rebase/lineage attribution precedence, dry-run classification, guaranteed-clean rollback). Route enforces done/archived-only and autoMerge-off guard rails; conflicting results are returned unresolved for sibling FN-7524 (AI-undo) to act on. Workspace tasks return `unsupported`.
|
||||
- 4707eb5: summary: Auto-approve now reliably sends specified plans to the board without a manual approval stop.
|
||||
category: fix
|
||||
dev: FN-7526 — investigated the reported "plans still park at awaiting-approval when auto-approve is on" symptom; resolvePlanApprovalRequired, mergeEffectiveSettings/applyWorkflowSettingsOverlay, and every finalizeApprovedTask call site (specifyTask, recoverApprovedTask, retryUnavailablePlanReview, tryFinalizeExplicitDuplicateMarker) already honored project planApprovalMode: "auto-approve-all" over a stored workflow requirePlanApproval value — no production defect reproduced. Added end-to-end regression coverage across every enumerated surface (Plan Review reviewer-outage retry, refinement routing, self-healing starved-refinement recovery) using the real mergeEffectiveSettings pipeline instead of isolated bare-settings unit calls, plus explicit assertions that the independent release-authorization and Workflow Plan Review gates remain intact under auto-approve-all, so a future bare-settings call site is caught immediately instead of silently reintroducing the reported behavior.
|
||||
- 3b52a4d: summary: Fix the in-dashboard Switch server menu not switching desktop local/remote.
|
||||
category: fix
|
||||
dev: The desktop shell's redirect effects in App.tsx read a dead `localServer` field that the preload never populates; extracted `resolveDesktopShellRedirectTarget` in appLifecycle.ts now derives the navigation target from the live `localRuntime`/`activeProfileId` state for both directions, and the unused `localServer` field was removed from `ShellConnectionState`.
|
||||
- 36bd74e: summary: Fix branch group completion checklists to show accurate landed/finished counts.
|
||||
category: fix
|
||||
dev: runAiMerge (the sole merge path since master-plan U0) never resolved branch-group routing or stamped mergeDetails.mergeTargetBranch/mergeTargetSource, so isBranchGroupMemberLanded permanently reported shared-group members as not landed. Routes through resolveBranchGroupMergeRouting (matching the legacy merger.ts pattern) and stamps the target fields on both the landed and no-op finalize paths; preserves merge-target-safety in isBranchGroupMemberLanded (a sibling/mismatched-branch member still never counts as landed).
|
||||
- df0be88: summary: Branch groups no longer report complete (or become promotable) when an unlanded member is archived.
|
||||
category: fix
|
||||
dev: listTasksByBranchGroup membership now scans with includeArchived:true so an archived-but-unlanded member stays counted in total instead of silently dropping out; mergeDetails is now persisted on ArchivedTaskEntry so an archived member that had already landed keeps counting as landed. evaluateBranchGroupCompletion / promoteBranchGroup gate correctly; merge-target-safety in isBranchGroupMemberLanded is unchanged.
|
||||
- ec9ac61: summary: Fix the global GitLab integration setting not persisting when saved.
|
||||
category: fix
|
||||
dev: splitSettingsSave now diffs the five global GitLab keys (gitlabEnabled, gitlabInstanceUrl, gitlabApiBaseUrl, gitlabAuthToken, gitlabAuthTokenType) against scoped global initials only, never the project-effective merged initialValues, so a project override no longer suppresses a real global save.
|
||||
- 8d36b99: summary: Fix task-detail Activity view dropdown not opening reliably on mobile.
|
||||
category: fix
|
||||
dev: Guards the Activity menu's window resize/orientationchange/scroll close-listener with the same opening-tap timing guard already used for visualViewport, and exempts scroll events originating in the `.detail-tabs` scroller, so a same-gesture mobile tap echo (Android/iOS, fixed modal or `.floating-window--task-detail` popup) no longer closes the menu the instant it opens.
|
||||
- ad744aa: summary: Manual "Run now" for the Database Backup automation now runs in-process like the scheduler, matching cron behavior.
|
||||
category: fix
|
||||
dev: The legacy single-command and command-step manual automation run path (`executeSingleCommand` in packages/dashboard/src/routes.ts) now intercepts `isInProcessBackupCommand`/`isInProcessMemoryBackupCommand` via the scoped TaskStore, mirroring `RoutineRunner.executeCommand`/`CronRunner`, instead of always shelling out via `exec()`. `formatInProcessBackupError`, `isInProcessBackupCommand`, and `isInProcessMemoryBackupCommand` are now exported from `@fusion/engine` for reuse. Existing onStep/onText live-run callbacks already stream incremental output for command/backup runs; added regression coverage confirming this holds for the new interception branch.
|
||||
- 5c3d58a: summary: Task cards no longer show the "Auto-recovery" oversight badge unless oversight is explicitly configured.
|
||||
category: fix
|
||||
dev: `TaskCard.tsx`'s `showOversightBadge` gate now also suppresses the badge when the effective level equals `DEFAULT_PLANNER_OVERSIGHT_LEVEL` ("autonomous") and there is no explicit per-task `plannerOversightLevel` override; an explicit per-task override of "autonomous" still renders the badge.
|
||||
- b4be515: summary: Remove the per-card overseer-state ("Executor") badge from task cards.
|
||||
category: fix
|
||||
dev: Deleted the FN-7516 `card-overseer-state-badge` render, its card-local `deriveOverseerCardWatchedStage` helper/label maps, and its CSS; the sibling oversight-level badge (`card-oversight-badge`) is unaffected.
|
||||
- 62ddb19: summary: Original task prompt now renders as Markdown and is collapsed by default in the task Plan tab.
|
||||
category: feature
|
||||
dev: Task Detail Plan/Definition tab original-prompt section reuses the existing `.detail-source-toggle`/`.detail-source-chevron--expanded` collapse pattern and the shared `ReactMarkdown` pipeline (`remarkGfm`, `sharedRehypePlugins`, `markdownLinkifyComponents`); backed by read-only `task.description`, no change to the generated `PROMPT.md` editor/revision flow.
|
||||
- 883c73e: summary: Fix agent-created artifacts not appearing live in the dashboard artifacts view.
|
||||
category: fix
|
||||
dev: Root cause was cross-instance artifact-registration replication, not the route/hook/render path (all already correct). `TaskStore.registerArtifact()` never bumped `lastModified`, and `checkForChanges()` (the polling replicator that lets a second TaskStore instance on the same project — e.g. the dashboard's cached store vs. the engine's own store — mirror events it did not write itself) only ever diffed the `tasks` table, never `artifacts`. A store instance that did not perform the write could therefore never observe or re-emit `artifact:registered`, leaving an already-open Documents/task Artifacts gallery stale until a full reload. Fixed by bumping `lastModified` on artifact writes and adding a strictly-increasing `rowid`-cursor poll over the `artifacts` table in `checkForChanges()`. See `packages/core/src/__tests__/artifacts.test.ts` and `packages/dashboard/src/routes/__tests__/artifacts-route-integration.test.ts` for regression coverage.
|
||||
- d09b57f: summary: Fix the mobile terminal shortcut bar so it scrolls horizontally to reach every key.
|
||||
category: fix
|
||||
dev: Added `min-width: 0` to `.terminal-shortcut-panel` to defeat the flex min-width:auto trap that clipped overflow instead of engaging `overflow-x: auto`.
|
||||
- 3d58260: summary: Planner-oversight intervention timeline now populates from real engine activity.
|
||||
category: fix
|
||||
dev: Wires PlannerOverseerMonitor/PlannerRecoveryController decision points to the FN-7520 emitOverseer\* façade with the real TaskStore; observation/escalation emission deduped per (task, stage[, signal]).
|
||||
- 052a277: summary: Show the "Global" prefix on the Authentication entry in the mobile Settings picker.
|
||||
category: fix
|
||||
dev: resolveSettingsSectionOptionLabel now derives the Global-group prefix for storage-less (scope: undefined) sections in SettingsModal.tsx (FN-7552).
|
||||
- 6e4c207: summary: Tasks held for release authorization or Plan Review are now shown distinctly, so auto-approve no longer looks broken.
|
||||
category: fix
|
||||
dev: FN-7559 — auto-approve-all bypasses only the manual plan-approval gate (unchanged, FN-7526). Release-authorization holds are surfaced with a new distinct status reason (`Task.awaitingApprovalReason: "release-authorization"`) and no longer render the generic manual Approve/Reject affordance in TaskCard/TaskDetailModal; Workflow Plan Review already used distinct statuses (`needs-replan`/`plan-review-unavailable`) and is unaffected. Both gates remain independent and intact — this is UI/data disambiguation only.
|
||||
- 6d364fc: summary: Move mobile terminal controls into a bottom footer so they no longer crowd the header, with a scrollable shortcut bar.
|
||||
category: fix
|
||||
dev: On the ≤768px terminal, the `.terminal-actions` cluster now renders in a `terminal-footer-actions` bar (with `min-width:0; overflow-x:auto`) instead of the header; desktop/floating/pinned-below keep the FN-7502 header layout. Preserves the FN-7550 shortcut-panel scroll fix.
|
||||
- b471aec: summary: Stop the release-authorization gate from holding tasks that merely disclaim releasing.
|
||||
category: fix
|
||||
dev: classifyReleaseTask now strips negated release-disclaimer clauses (e.g. "this task performs no release/publish; releases are owned by scripts/release.mjs") before signal matching in packages/engine/src/triage-release-authorization.ts, so revert/undo/UI specs are no longer false-flagged as release-class. Genuine "run pnpm release"/"publish @runfusion/fusion" intent still trips the gate.
|
||||
- 9d4a45b: summary: Fix mobile terminal text still rendering with excess inter-character gaps after font-load settle.
|
||||
category: fix
|
||||
dev: Root cause: xterm's OptionsService setter is a no-op when reassigning an already-current fontFamily/fontSize, so post-settle reapply never forced CharSizeService/DomRenderer to remeasure. Added `forceTerminalFontRemeasure()` in `terminalPreferences.ts`, used by both `TerminalModal.tsx` and `SessionTerminal.tsx` at every post-`waitForTerminalFontMetrics()` settle site.
|
||||
- 72b77bf: summary: Stop Plan Review from looping tasks forever and fix its "can't find the plan" reviews.
|
||||
category: fix
|
||||
dev: FN-7561 — Plan Review pre-merge gate hardening in packages/engine/src/executor.ts. (1) The reviewer ran readonly with cwd=worktree but the spec lives at project-root .fusion/tasks/<id>/PROMPT.md, so "Read PROMPT.md" produced "no PROMPT.md found / data is in a DB" non-verdicts; the spec text is now injected into the reviewer prompt via readTaskArtifact. (2) A malformed reviewer response now self-retries once on the primary model when no fallback is configured. (3) A malformed (advisory_failure, no verdict) plan-review result can never trigger a triage replan. (4) The unbounded plan-review replan default is capped at 15 attempts with a loud halting log entry, so a persistently-disagreeing planner/reviewer no longer burns LLM calls indefinitely (FN-7525 ran 13+ attempts overnight).
|
||||
- c08498e: summary: Planner-overseer task badge now shows a readable label and explains what it is waiting on.
|
||||
category: fix
|
||||
dev: TaskCard badge renders plannerOverseerStateLabel + plannerOverseerBadgeTooltip built from the existing PlannerOverseerRuntimeSnapshot (reason/watchedStage/signal/pendingConfirmation); presentation-only, no engine changes.
|
||||
- 24b27e8: summary: Plan approve/reject API now blocks release-authorization holds, requiring the authorization marker first.
|
||||
category: fix
|
||||
dev: FN-7564 — POST /tasks/:id/approve-plan and /reject-plan now return 400 when task.awaitingApprovalReason === "release-authorization" (FN-7559 discriminator), enforcing the FN-6481 release-authorization gate at the API layer regardless of client. Manual-approval holds are unaffected.
|
||||
- fb45157: summary: Pin the mobile terminal close (X) button to the top-right corner so it is easy to find and tap.
|
||||
category: fix
|
||||
dev: On the ≤768px terminal, the `terminal-close` button now carries a `terminal-close--corner` class (order:3 + margin-inline-start:auto) so it renders last in flex order and hugs the right edge next to the tab dropdown, instead of falling back to order:0 (far left). Desktop/floating/pinned-below placement inside `.terminal-actions` is unchanged.
|
||||
- 7c0be53: summary: Fix mobile terminal excess character spacing that survived earlier font-remeasure fixes.
|
||||
category: fix
|
||||
dev: `TerminalModal`/`SessionTerminal` re-bake xterm's `DomRenderer` letter-spacing compensation AFTER `fitAddon.fit()` settles the post-fit column count (not just before it), since `handleResize()` never re-bakes spacing itself. See `docs/solutions/ui-bugs/xterm-options-noop-remeasure-after-font-settle.md` recurrence #4.
|
||||
- 71dfd3a: summary: Rename downloadable CLI release binaries to the fn-cli-<platform> base name.
|
||||
category: internal
|
||||
dev: `binaryNameForTarget` in `packages/cli/build.ts` and the `release.yml` / `test-release.yml` matrices now emit `fn-cli-<suffix>`; the local dev binary stays `fn`/`fn.exe`.
|
||||
- 9592e3a: summary: Manual plan approval no longer re-asks you to approve a plan you already approved when it hasn't changed.
|
||||
category: fix
|
||||
dev: FN-7569 — approving a plan records a fingerprint of the approved PROMPT.md (new nullable Task.approvedPlanFingerprint, migration 139). The manual plan-approval gate skips re-parking at awaiting-approval when a re-specification (replan, plan-review retry, self-healing rebound) produces the same plan; a changed plan or reject-plan still requires fresh approval. Release authorization, Workflow Plan Review, and auto-approve-all are unchanged.
|
||||
- c31f9ef: summary: Move the planner intervention timeline into the task Activity view dropdown.
|
||||
category: feature
|
||||
dev: Removes the inline `PlannerInterventionTimeline` mount from the FN-7517 oversight cluster in `TaskDetailModal.tsx` and adds a fourth `interventions` `ActivitySegment`, shown in the Activity dropdown only when planner oversight is active for the task; falls back to Live if oversight turns off while Interventions is selected.
|
||||
- ce9df29: summary: Expired Claude subscription logins now show disconnected with a re-login prompt; tokens auto-refresh before expiry.
|
||||
category: fix
|
||||
dev: Unifies OAuth expiry detection between OAuthExpiryMonitor and /api/auth/status, and adds an engine-side proactive OAuth refresh scheduler wired in project-engine (guarded by skipNotifier). No token material logged.
|
||||
- 196abb5: summary: Anthropic subscription reads now refresh the OAuth token automatically instead of silently failing when expired.
|
||||
category: fix
|
||||
dev: mergeAuthStorageReads getApiKey("anthropic-subscription") now delegates to the underlying engine authStorage.getApiKey (the only refresh-token HTTP round trip) instead of a local static expiry check; regression tests drive the wrapper directly. No token material logged.
|
||||
- 45e5a26: summary: Stop GitHub tracking-issue creation from linking new tasks to old/closed issues.
|
||||
category: fix
|
||||
dev: github-tracking dedup now only reuses OPEN issues and requires a File-Scope path overlap (keyword-only matches no longer link). Prevents mis-linking a fresh task to a stale/resolved tracking issue (FN-7579). Setting `githubTrackingDedupEnabled` unchanged.
|
||||
- a1a6b09: summary: Clarify the oversight "Nudge unavailable" guideline so it no longer reads as an overseer fault.
|
||||
category: fix
|
||||
dev: TaskDetailModal oversight controls — reworded taskDetail.oversight.nudgeDisabledTitle and added taskDetail.oversight.nudgeSuppressedTitle to differentiate periodic-observation vs. manual-control states. No enablement/engine logic changed.
|
||||
- cf3fe8b: summary: New tasks created under the Coding (Ideas) workflow now land in the Ideas column and wait for you to promote them.
|
||||
category: fix
|
||||
dev: Dashboard create surfaces (InlineCreateCard, QuickEntryBox, NewTaskModal, insight/todo → task) no longer hard-code column:"triage"; the store now resolves the selected/default workflow's intake column. InlineCreateCard forwards workflowId at create time instead of applying it post-create. Also fixed a glue-layer regression in `useTaskHandlers.ts` (`handleBoardQuickCreate`/`handleModalCreate`) that re-forced column:"triage" even after the UI surfaces stopped sending it.
|
||||
- 8b4e522: summary: Fix tasks vanishing from the board after being added to a workflow like Coding (Ideas).
|
||||
category: fix
|
||||
dev: Board.tsx forces a board-workflows refetch (deferred one tick, signature-guarded) whenever a rendered task is missing from the taskWorkflowIds map, so its real workflow and intake column resolve regardless of which create surface added it; the single-workflow grouping also re-homes a task whose column its workflow no longer declares into the intake lane instead of dropping it. Fixes the FN-7591 regression where intake-column cards (column "ideas") fell back to the default workflow, which has no such column, and were filtered out until a manual reload.
|
||||
- f30d55f: summary: Move the Before → After transformation summary to the top of generated task definitions.
|
||||
category: fix
|
||||
dev: Reorders the standard and fast triage `PROMPT.md` templates in packages/core/src/agent-prompts.ts so `## Before → After Transformation` is the first content section, ahead of `## Review Level` and `## Mission`, matching FN-7499's glance-verification intent.
|
||||
- 20379e8: summary: Task-detail Priority dropdown now matches the Oversight dropdown's size, border, and typography.
|
||||
category: fix
|
||||
dev: Removed the Priority-only forced select/option uppercase, added a neutral chip background scoped to `.detail-priority-chip.card-priority-badge--normal` for the untinted `normal` level, and reused the FN-7585 shared `--btn-border-width`/`--border`/`--detail-control-border-radius`/`--detail-priority-control-min-height` tokens so both dropdowns render as one control style across desktop and the mobile oversight-overflow surface.
|
||||
- e0f3d3d: summary: Default workflow boards now label the intake column "Planning" instead of "Triage".
|
||||
category: fix
|
||||
dev: Renamed the `name` of the `id: "triage"` intake column to "Planning" in builtin-coding, builtin-stepwise-coding, and builtin-pr workflow IRs (column id unchanged; linear built-ins inherit via canonicalBuiltinWorkflowColumns). COLUMN_LABELS.triage was already "Planning".
|
||||
- 5b193d2: summary: Fix the task-detail Nudge control staying disabled when the overseer is actively watching.
|
||||
category: fix
|
||||
dev: GET /api/tasks/:id now attaches the transient plannerOverseerState snapshot (mirrors the list route); TaskDetailModal reads the snapshot from workingTask so detail refetches no longer drop it.
|
||||
- 546ef16: summary: Honor mission branchStrategy when triage omits branchAssignment; skip validation for inactive missions.
|
||||
category: fix
|
||||
dev: resolveBranchAssignmentContext returns undefined for absent mode so triage falls back to mission.branchStrategy; processTaskOutcome gates on mission.status === "active" like recoverActiveMissions.
|
||||
- b173f76: summary: Planner overseer no longer marks healthy in-progress tasks as "recovering" or steers them.
|
||||
category: fix
|
||||
dev: `decidePlannerRecovery` now returns `none` for healthy (`progressing`/`complete`) and `awaiting-human` executor/workflow-gate signals instead of falling through to `inject_guidance`; only `stuck`/`blocked`/`failed` trigger autonomous steering. Also dedupes the `PlannerOverseerMonitor` activity-feed heartbeat so an unchanged `(stage, signal, reason)` observation is logged once per change, not every poll tick. Fixes the "overseer recovering" badge appearing on every autonomous card and the needless AI-consuming guidance injections (FN-7577).
|
||||
|
||||
### runfusion.ai
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- Updated dependencies [8668a05]
|
||||
- Updated dependencies [978cdda]
|
||||
- Updated dependencies [635fca2]
|
||||
- Updated dependencies [3d55102]
|
||||
- Updated dependencies [0f1cd0a]
|
||||
- Updated dependencies [b42ba9f]
|
||||
- Updated dependencies [a7559b0]
|
||||
- Updated dependencies [4b530a6]
|
||||
- Updated dependencies [a5ac3c3]
|
||||
- Updated dependencies [8912399]
|
||||
- Updated dependencies [d16c8b4]
|
||||
- Updated dependencies [b800f7d]
|
||||
- Updated dependencies [315f3bc]
|
||||
- Updated dependencies [9dc248e]
|
||||
- Updated dependencies [52dbc0e]
|
||||
- Updated dependencies [ced783e]
|
||||
- Updated dependencies [50cdab1]
|
||||
- Updated dependencies [50786f2]
|
||||
- Updated dependencies [b4b1f6d]
|
||||
- Updated dependencies [e8b7362]
|
||||
- Updated dependencies [0900a38]
|
||||
- Updated dependencies [a2b09f2]
|
||||
- Updated dependencies [0f05156]
|
||||
- Updated dependencies [20184ac]
|
||||
- Updated dependencies [82493e0]
|
||||
- Updated dependencies [5689346]
|
||||
- Updated dependencies [b42be87]
|
||||
- Updated dependencies [2f23d22]
|
||||
- Updated dependencies [efa8105]
|
||||
- Updated dependencies [61c8bdc]
|
||||
- Updated dependencies [e8dc2ae]
|
||||
- Updated dependencies [d2e3134]
|
||||
- Updated dependencies [b0208c1]
|
||||
- Updated dependencies [7d8a1b8]
|
||||
- Updated dependencies [2797803]
|
||||
- Updated dependencies [a2d6349]
|
||||
- Updated dependencies [4baa4c4]
|
||||
- Updated dependencies [87a700c]
|
||||
- Updated dependencies [68f5153]
|
||||
- Updated dependencies [aa757bc]
|
||||
- Updated dependencies [0689250]
|
||||
- Updated dependencies [12a6d1b]
|
||||
- Updated dependencies [81f2053]
|
||||
- Updated dependencies [2cc84b5]
|
||||
- Updated dependencies [79ab367]
|
||||
- Updated dependencies [c16cc9e]
|
||||
- Updated dependencies [aae603b]
|
||||
- Updated dependencies [d10ea9a]
|
||||
- Updated dependencies [bf68839]
|
||||
- Updated dependencies [53d7b7e]
|
||||
- Updated dependencies [c4d81fe]
|
||||
- Updated dependencies [e7cb2f1]
|
||||
- Updated dependencies [4707eb5]
|
||||
- Updated dependencies [3b52a4d]
|
||||
- Updated dependencies [5ad8ec8]
|
||||
- Updated dependencies [726cbf8]
|
||||
- Updated dependencies [36bd74e]
|
||||
- Updated dependencies [df0be88]
|
||||
- Updated dependencies [ec9ac61]
|
||||
- Updated dependencies [8d36b99]
|
||||
- Updated dependencies [ad744aa]
|
||||
- Updated dependencies [5c3d58a]
|
||||
- Updated dependencies [b4be515]
|
||||
- Updated dependencies [62ddb19]
|
||||
- Updated dependencies [883c73e]
|
||||
- Updated dependencies [2ed06f9]
|
||||
- Updated dependencies [8c6f76c]
|
||||
- Updated dependencies [d09b57f]
|
||||
- Updated dependencies [3d58260]
|
||||
- Updated dependencies [052a277]
|
||||
- Updated dependencies [f992e6a]
|
||||
- Updated dependencies [2df6c35]
|
||||
- Updated dependencies [94e9d15]
|
||||
- Updated dependencies [3dd227b]
|
||||
- Updated dependencies [6e4c207]
|
||||
- Updated dependencies [6d364fc]
|
||||
- Updated dependencies [b471aec]
|
||||
- Updated dependencies [9d4a45b]
|
||||
- Updated dependencies [72b77bf]
|
||||
- Updated dependencies [c08498e]
|
||||
- Updated dependencies [24b27e8]
|
||||
- Updated dependencies [fb45157]
|
||||
- Updated dependencies [7c0be53]
|
||||
- Updated dependencies [71dfd3a]
|
||||
- Updated dependencies [9592e3a]
|
||||
- Updated dependencies [c31f9ef]
|
||||
- Updated dependencies [ce9df29]
|
||||
- Updated dependencies [78d4db9]
|
||||
- Updated dependencies [196abb5]
|
||||
- Updated dependencies [7435849]
|
||||
- Updated dependencies [73b38ba]
|
||||
- Updated dependencies [42bbe58]
|
||||
- Updated dependencies [45e5a26]
|
||||
- Updated dependencies [a1a6b09]
|
||||
- Updated dependencies [53fe0d7]
|
||||
- Updated dependencies [cf3fe8b]
|
||||
- Updated dependencies [8b4e522]
|
||||
- Updated dependencies [f30d55f]
|
||||
- Updated dependencies [20379e8]
|
||||
- Updated dependencies [e0f3d3d]
|
||||
- Updated dependencies [5b193d2]
|
||||
- Updated dependencies [ecbbb29]
|
||||
- Updated dependencies [546ef16]
|
||||
- Updated dependencies [b173f76]
|
||||
- @runfusion/fusion@0.56.0
|
||||
|
||||
## 0.55.0
|
||||
|
||||
### @fusion/dashboard
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- @fusion/core@0.55.0
|
||||
- @fusion/engine@0.55.0
|
||||
- @fusion/i18n@0.39.19
|
||||
- @fusion-plugin-examples/cli-printing-press@0.1.36
|
||||
- @fusion-plugin-examples/compound-engineering@0.1.19
|
||||
- @fusion-plugin-examples/dependency-graph@0.1.50
|
||||
- @fusion-plugin-examples/roadmap@0.1.38
|
||||
- @fusion-plugin-examples/cursor-runtime@0.1.38
|
||||
- @fusion-plugin-examples/droid-runtime@0.1.45
|
||||
- @fusion-plugin-examples/hermes-runtime@0.2.69
|
||||
- @fusion-plugin-examples/openclaw-runtime@0.2.69
|
||||
- @fusion-plugin-examples/paperclip-runtime@0.2.69
|
||||
|
||||
### @fusion/desktop
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- @fusion/core@0.55.0
|
||||
- @fusion/dashboard@0.55.0
|
||||
- @fusion/engine@0.55.0
|
||||
|
||||
### @fusion/engine
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- @fusion/core@0.55.0
|
||||
- @fusion/pi-claude-cli@0.55.0
|
||||
|
||||
### @fusion/plugin-sdk
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- @fusion/core@0.55.0
|
||||
|
||||
### @runfusion/fusion
|
||||
|
||||
#### Minor Changes
|
||||
|
||||
- 8580910: summary: Allow configuring permissions for ephemeral and permanent agents.
|
||||
category: feature
|
||||
dev: Applies capability grants and runtime permission policies consistently across agent lifetimes.
|
||||
- 5738766: summary: Add GitLab instance URL settings for GitLab.com and self-managed servers.
|
||||
category: feature
|
||||
dev: Adds typed GitLab web/API URL configuration and dashboard controls for later GitLab integration subtasks.
|
||||
- fafe4c9: summary: Add GitLab access-token settings for personal, project, and group tokens.
|
||||
category: feature
|
||||
dev: Documents required GitLab API scopes and adds token auth resolution for later GitLab integration tasks.
|
||||
- 865dec2: summary: Add GitLab project issue, group issue, and merge request imports.
|
||||
category: feature
|
||||
dev: Adds HTTP API GitLab import routes, dashboard affordances, CLI commands, and extension tools.
|
||||
- 34be333: summary: Add GitLab comment and auto-close lifecycle actions for linked work items.
|
||||
category: feature
|
||||
dev: Uses GitLab REST notes and state-event APIs with configured self-managed instance URLs.
|
||||
- 1f6befc: summary: Add GitLab as a Command Center Signals connector.
|
||||
category: feature
|
||||
dev: Adds GitLab webhook token verification and issue/MR signal normalization for Command Center incidents.
|
||||
- 91737b1: summary: Add explicit onboarding choices to use, initialize, or clone a git repository.
|
||||
category: feature
|
||||
dev: Setup wizard now sends gitSetupMode while preserving cloneUrl-only project registration clients.
|
||||
- 91fb53f: summary: Let chat update existing agents without delete/recreate.
|
||||
category: feature
|
||||
dev: Adds the fn_agent_update Pi extension tool for scoped AgentStore.updateAgent config edits.
|
||||
- dfb6e52: summary: Add a bundled Linear import plugin for creating tasks from Linear issues.
|
||||
category: feature
|
||||
dev: Ships fusion-plugin-linear-import with plugin settings, routes, tools, dashboard view, and bundled-plugin packaging.
|
||||
- ec0256f: summary: Add a mandatory Planning Mode deepening checkpoint before final summaries.
|
||||
category: feature
|
||||
dev: Planning sessions now persist pending summaries behind a "Would you like to go deeper?" checkpoint.
|
||||
- 5984f32: summary: Add visible create buttons and recursive search to Project Files.
|
||||
category: feature
|
||||
dev: Files — Project uses the existing workspace-safe create and /files/search APIs with settings pickers left compact.
|
||||
- f973334: summary: Add a GitLab enable toggle and collapsible Settings controls.
|
||||
category: feature
|
||||
dev: Adds gitlabEnabled gating for GitLab API operations while preserving saved configuration.
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- 306e516: summary: Stop recurring Windows Terminal warning popups during terminal startup.
|
||||
category: fix
|
||||
dev: Keeps embedded terminal bootstrap on supported shells and surfaces actionable inline errors.
|
||||
- 9df13c7: summary: Fix dashboard localStorage quota exhaustion from stale SWR caches and add a Clear local data escape hatch.
|
||||
category: fix
|
||||
dev: Stale SWR hydration entries (per-chat-session/per-room message caches) were never garbage-collected; readCache now lazily deletes stale entries, a boot sweep prunes anything older than 24h, and Settings → General exposes a user-facing "Clear local data" button that preserves the auth token.
|
||||
- c7641f9: summary: Fix `fusion desktop` on Windows and published npm installs (Electron dependency, GPU/sandbox flags, dashboard reuse).
|
||||
category: fix
|
||||
dev: `packages/cli/package.json` now depends on `electron` at runtime; previously the desktop launcher called `require("electron")`, which is only available inside the source checkout (via `pnpm-workspace.yaml` `onlyBuiltDependencies`) and is missing for npm consumers, causing `fusion desktop` to hang or fail silently. The launcher now applies GPU/sandbox-disabling Electron flags only on Windows (`os.platform() === "win32"`), keeps hardware acceleration and the Chromium sandbox on macOS/Linux, exports `FUSION_SERVER_PORT` so the desktop reuses the CLI-started dashboard instead of double-binding ports, and isolates desktop user-data under `~/.fusion/desktop-user-data`. Relocating the profile performs a one-time copy of the previous default Electron profile (`user-data-migration.ts`) so upgrading operators keep window geometry/session. `packages/desktop/scripts/build.ts` now fails the build if `main.js`/`preload.js`/`client/index.html` are missing from `dist/` or the staged `deploy/dist/`, preventing shipping an incomplete `app.asar`.
|
||||
- da662c4: summary: First-run agent setup no longer errors on a duplicate CEO; desktop Switch-server button now opens the connection menu.
|
||||
category: fix
|
||||
dev: Onboarding agent creation (ModelOnboardingModal + SetupWizardModal) treats a 409 "Agent with this name already exists" as success and advances, since the default CEO can be created from more than one first-run surface. The desktop preload now bridges the `shell:open-connection-manager` IPC (sent by main when the header Switch-server button is clicked) into the `window` DOM event ShellContext listens for, so NativeShellConnectionManager (Local/Remote toggle + remote profiles) actually opens.
|
||||
- 377eee6: summary: Allow operators to delete archived tasks.
|
||||
category: fix
|
||||
dev: Extends task deletion to archive-db snapshots while preserving soft-delete tombstones and ID reservation.
|
||||
- 9ee33f9: summary: Show workflow template block boundary connectors in the graph editor.
|
||||
category: fix
|
||||
dev: Adds visual-only foreach/loop/optional-group template boundary edges that are filtered from persisted IR.
|
||||
- 3453d16: summary: Preserve the selected dashboard project across browser refreshes.
|
||||
category: fix
|
||||
dev: Project selection now updates and hydrates from the existing `?project=` dashboard URL contract.
|
||||
- 777f647: summary: Detect Cursor CLI installations that expose Windows cmd or bat shims.
|
||||
category: fix
|
||||
dev: Cursor runtime probes and model discovery now shell-spawn only on Windows and preserve spawn diagnostics.
|
||||
- abb1917: summary: Add a Settings override for the local Cursor CLI binary path.
|
||||
category: feature
|
||||
dev: Adds global `cursorCliBinaryPath` and threads it through Cursor CLI probes, auth status, enable validation, and model discovery.
|
||||
- b8e126e: summary: Display linked GitLab tracking metadata and stale badges on tasks.
|
||||
category: feature
|
||||
dev: Persists GitLab tracking metadata separately from GitHub tracking fields.
|
||||
- c49a933: summary: Preserve GitLab tracking metadata for CLI and extension imports.
|
||||
category: fix
|
||||
dev: GitLab project, group, and merge-request imports now carry gitlabTracking metadata alongside sourceIssue provenance.
|
||||
- 53d5bb1: summary: Keep Planning Mode Refine Further from getting stuck on duplicate generation.
|
||||
category: fix
|
||||
dev: Guards completed-summary refinement as a single-flight UI turn and preserves the active planning stream on same-refine in-progress responses.
|
||||
- b493a1e: summary: Show task status badges on Documents task groups.
|
||||
category: fix
|
||||
dev: DocumentsView now renders taskColumn metadata in task document group headers and covers collapsed done/non-done states.
|
||||
- e5be924: summary: Suppress misleading Anthropic Subscription re-login banners when another Anthropic auth method is active.
|
||||
category: fix
|
||||
dev: Keeps subscription OAuth expired in Settings while hiding only the global urgent banner entry when API key or Claude CLI auth is active.
|
||||
- 326c72b: summary: Keep Anthropic authentication cards grouped near the top in Settings.
|
||||
category: fix
|
||||
dev: Sorts Claude CLI, Anthropic Subscription, and Anthropic API Key before other auth cards within each auth group.
|
||||
- 1d6bb08: summary: Stop planner model fallback loops with a clear terminal triage error.
|
||||
category: fix
|
||||
dev: Bounds prompt-time/session-creation model fallback exhaustion and persists failed triage state.
|
||||
- aa8f1f3: summary: Recover stale task branch-group references from Task Detail after server restarts.
|
||||
category: fix
|
||||
dev: Adds branch_group restart regression coverage and non-origin integration-branch diagnostics.
|
||||
- 72adb52: summary: Add sidebar rename buttons to direct Chat conversations.
|
||||
category: feature
|
||||
dev: Reuses ChatView's existing rename dialog and useChat renameSession path.
|
||||
- 25fecd7: summary: Return GitHub issue import actions to the main issue list.
|
||||
category: fix
|
||||
dev: Updates Import Tasks issue import/close navigation and regression coverage.
|
||||
- ffcb54b: summary: Prevent Planning Mode sessions from failing when MCP resolution returns no shaped result.
|
||||
category: fix
|
||||
dev: Dashboard planning lanes now default malformed MCP resolver output to an empty server set while preserving MCP forwarding.
|
||||
- 8d7abb8: summary: Fix Android mobile terminal spacing while the keyboard is open.
|
||||
category: fix
|
||||
dev: Terminal mobile sizing now tracks visualViewport width for keyboard-open xterm fits.
|
||||
- 22fd0da: summary: Fix Last 30 days token usage to include every model in Command Center.
|
||||
category: fix
|
||||
dev: Corrects Command Center token analytics range attribution for durable multi-model task usage.
|
||||
- 765218f: summary: Include supported chat interactions in Command Center token usage totals.
|
||||
category: fix
|
||||
dev: Records chat-session and room-responder token usage separately from task execution tokens and aggregates both sources in token analytics.
|
||||
- 1d6011c: summary: Collapse mobile Chat thread controls into one compact header row.
|
||||
category: feature
|
||||
dev: Mobile direct-chat moves back/session controls into ViewHeader and floats the Markdown/plain toggle.
|
||||
- 2050323: summary: Preserve workflow setting edits made while a values save is still in flight.
|
||||
category: fix
|
||||
dev: WorkflowSettingsPanel and Project Models workflow lane saves now clear only snapshot-matching pending keys.
|
||||
- 3fac409: summary: Preserve migrated workflow settings when project identity is assigned later.
|
||||
category: fix
|
||||
dev: Backfills rootDir-keyed workflow_settings rows into the durable project identity row, keeping identity values on conflicts.
|
||||
- 78ebeaf: summary: Show the bundled Linear Import plugin in Plugin Manager and dashboard plugin surfaces.
|
||||
category: fix
|
||||
dev: Keeps fusion-plugin-linear-import registered across the built-in Plugin Manager catalog while reusing existing registry, dashboard view, and bundled packaging paths.
|
||||
- 1430a42: summary: Fix the mobile Chat header so back navigation and session selection stay on one row.
|
||||
category: fix
|
||||
dev: Keeps the direct-chat mobile header collapsed while preserving desktop and room-chat layouts.
|
||||
- 7a4b0bf: summary: Fix iOS mobile terminal spacing when opening terminals with the keyboard already visible.
|
||||
category: fix
|
||||
dev: Seeds iOS keyboard-open viewport baselines for TerminalModal and SessionTerminal before xterm fit/resize.
|
||||
- 00afb22: summary: Default fresh startup and theme reset to System mode.
|
||||
category: fix
|
||||
dev: Fresh global settings, pre-hydration scripts, and Appearance reset now keep Shadcn Ember while following OS light/dark preference.
|
||||
- 9532520: summary: Remember task popup size and position when switching between tasks.
|
||||
category: fix
|
||||
dev: Task-detail FloatingWindow instances share the `floating-window:task-detail` geometry key.
|
||||
- 7300bf5: summary: Fix iPhone Safari terminal text spacing with the keyboard open.
|
||||
category: fix
|
||||
dev: Disables WebKit text-size adjustment inside dashboard xterm measurement subtrees.
|
||||
- 30a11ac: summary: Count planning tasks correctly in the dashboard footer queue metric.
|
||||
category: fix
|
||||
dev: Footer counter tests now cover queued, running, stuck, blocked, review, overlap, background AI, and Done absence.
|
||||
- d9ef514: summary: Show conversation titles in the mobile Chat dropdown.
|
||||
category: fix
|
||||
dev: Keeps the provider logo while removing model-name text from the mobile direct-chat trigger.
|
||||
- e4349ee: summary: Merges no longer fail when a task adds a dependency without updating the lockfile.
|
||||
category: fix
|
||||
dev: In merge-dependency-sync.ts, an inferred frozen install (pnpm/yarn/bun) that fails with an outdated-lockfile error now retries once non-frozen (pnpm gets explicit --no-frozen-lockfile) to regenerate the lockfile in the clean-room worktree, recomputing the install marker. Configured worktreeInitCommand keeps its authoritative frozen intent and still hard-fails. Surfaced via the merge:ai-deps-sync run-audit event (healed/healedCommand).
|
||||
|
||||
### runfusion.ai
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- Updated dependencies [306e516]
|
||||
- Updated dependencies [9df13c7]
|
||||
- Updated dependencies [c7641f9]
|
||||
- Updated dependencies [da662c4]
|
||||
- Updated dependencies [377eee6]
|
||||
- Updated dependencies [9ee33f9]
|
||||
- Updated dependencies [8580910]
|
||||
- Updated dependencies [3453d16]
|
||||
- Updated dependencies [777f647]
|
||||
- Updated dependencies [abb1917]
|
||||
- Updated dependencies [5738766]
|
||||
- Updated dependencies [fafe4c9]
|
||||
- Updated dependencies [865dec2]
|
||||
- Updated dependencies [b8e126e]
|
||||
- Updated dependencies [34be333]
|
||||
- Updated dependencies [1f6befc]
|
||||
- Updated dependencies [c49a933]
|
||||
- Updated dependencies [53d5bb1]
|
||||
- Updated dependencies [91737b1]
|
||||
- Updated dependencies [b493a1e]
|
||||
- Updated dependencies [e5be924]
|
||||
- Updated dependencies [326c72b]
|
||||
- Updated dependencies [1d6bb08]
|
||||
- Updated dependencies [aa8f1f3]
|
||||
- Updated dependencies [91fb53f]
|
||||
- Updated dependencies [72adb52]
|
||||
- Updated dependencies [25fecd7]
|
||||
- Updated dependencies [dfb6e52]
|
||||
- Updated dependencies [ec0256f]
|
||||
- Updated dependencies [5984f32]
|
||||
- Updated dependencies [ffcb54b]
|
||||
- Updated dependencies [8d7abb8]
|
||||
- Updated dependencies [22fd0da]
|
||||
- Updated dependencies [765218f]
|
||||
- Updated dependencies [1d6011c]
|
||||
- Updated dependencies [2050323]
|
||||
- Updated dependencies [3fac409]
|
||||
- Updated dependencies [f973334]
|
||||
- Updated dependencies [78ebeaf]
|
||||
- Updated dependencies [1430a42]
|
||||
- Updated dependencies [7a4b0bf]
|
||||
- Updated dependencies [00afb22]
|
||||
- Updated dependencies [9532520]
|
||||
- Updated dependencies [7300bf5]
|
||||
- Updated dependencies [30a11ac]
|
||||
- Updated dependencies [d9ef514]
|
||||
- Updated dependencies [e4349ee]
|
||||
- @runfusion/fusion@0.55.0
|
||||
|
||||
## 0.54.0
|
||||
|
||||
@@ -12122,6 +12789,22 @@ for reference.
|
||||
- Updated dependencies [a2ed6d0]
|
||||
- @runfusion/fusion@0.1.0
|
||||
|
||||
## 0.39.21
|
||||
|
||||
### @fusion/i18n
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- @fusion/core@0.56.1
|
||||
|
||||
## 0.39.20
|
||||
|
||||
### @fusion/i18n
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- @fusion/core@0.56.0
|
||||
|
||||
## 0.39.19
|
||||
|
||||
### @fusion/i18n
|
||||
@@ -12276,6 +12959,22 @@ for reference.
|
||||
|
||||
- @fusion/core@0.40.0
|
||||
|
||||
## 0.11.47
|
||||
|
||||
### @fusion/droid-cli
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- @fusion-plugin-examples/droid-runtime@0.1.47
|
||||
|
||||
## 0.11.46
|
||||
|
||||
### @fusion/droid-cli
|
||||
|
||||
#### Patch Changes
|
||||
|
||||
- @fusion-plugin-examples/droid-runtime@0.1.46
|
||||
|
||||
## 0.11.45
|
||||
|
||||
### @fusion/droid-cli
|
||||
|
||||
12
MOBILE.md
12
MOBILE.md
@@ -89,6 +89,16 @@ Implementation notes:
|
||||
- Active-profile deletion fallback is shell-owned: deleting the active profile promotes the first remaining profile, and deleting the final profile resets to a clean empty state.
|
||||
- The dashboard consumes this through the shared `window.fusionShell` connection APIs.
|
||||
|
||||
### Native Back Handling (Android Back + iOS Edge-Swipe-Back)
|
||||
|
||||
Task-detail dismissal via native "back" (Android hardware Back / predictive-back gesture,
|
||||
iOS edge-swipe-back, or plain browser swipe-back) converges on a single shared invariant:
|
||||
the dashboard's `useNavigationHistory` nav-history stack. See `packages/mobile/README.md`
|
||||
→ "Native Back Handling" for the full Android (`fusion:native-back`) vs. iOS (`popstate`)
|
||||
routing details and the tracked post-`cap sync` patch scripts (`scripts/
|
||||
patch-android-manifest.ts`, `scripts/patch-ios-webview.ts`) that keep each platform's native
|
||||
gesture delivery enabled across `cap sync` regenerations.
|
||||
|
||||
### Planning Mode
|
||||
|
||||
Planning Mode opens directly into the composer pane on mobile when no planning sessions exist, avoiding an empty-sidebar dead end. On desktop/tablet the split view is unaffected. Once sessions are saved, mobile shows the session list as usual and the user can navigate between list and detail panes.
|
||||
@@ -186,3 +196,5 @@ Mobile package scripts (`packages/mobile/package.json`):
|
||||
- `dev:ios`
|
||||
- `dev:android`
|
||||
- `build:mobile`
|
||||
- `patch:ios-webview` — idempotently enables the iOS WKWebView edge-swipe-back gesture in the generated `ios/App/App/AppDelegate.swift` (safe no-op if `ios/` doesn't exist yet)
|
||||
- `capacitor:sync:after` — Capacitor's own post-`cap sync` hook; currently runs the iOS webview patch so `cap sync` regeneration can't silently drop the gesture opt-in
|
||||
|
||||
11
README.md
11
README.md
@@ -136,6 +136,7 @@ Every task shows its plan, its reviews, its diffs, and its file changes in real
|
||||
|---|---|
|
||||
| 🧠 **AI planning** | Describe a task in plain language. Planning agents turn it into a `PROMPT.md` plan with steps, file scope, and acceptance criteria. |
|
||||
| 🔁 **Selectable workflows** | Built-ins cover coding, quick fixes, review-heavy work, stepwise execution, plugin-gated Compound Engineering, and PR lifecycle fragments. Pick a workflow per task or author custom ones in the [Workflow Editor](./docs/workflow-editor.md). |
|
||||
| 🛡️ **Planner oversight** | Per-task or per-workflow oversight level (`off` / `observe` / `steer` / `autonomous`) governs how closely a planner overseer watches and intervenes — merge/PR and destructive actions always require explicit human confirmation. See [Settings Reference](./docs/settings-reference.md#workflow-settings) and [Dashboard Guide](./docs/dashboard-guide.md). |
|
||||
| 🌳 **Worktree isolation** | Each task runs in its own branch and worktree (`fusion/{task-id}`). Parallel tasks. Zero conflicts. Optional [worktrunk](https://github.com/max-sixty/worktrunk) delegation via [`worktrunk.enabled`](./docs/settings-reference.md#worktree-backend-settings) (see [WorktreeBackend abstraction](./docs/architecture.md#worktreebackend-abstraction)). |
|
||||
| ⚡ **Smart merge controls** | Passing every gate? Fusion squash-merges and moves on. Opt into manual approval anywhere, inherit the live global auto-merge default, or set explicit per-task auto/manual overrides. |
|
||||
| 🛰️ **Multi-node mesh** | Laptop, Mac mini, Linux server, cloud VM, phone — all synced. Desktop, mobile, web. |
|
||||
@@ -385,6 +386,15 @@ Fusion workflows define how a task moves from idea to delivery. The default codi
|
||||
|
||||
Read [Workflow Steps](./docs/workflow-steps.md) for runtime semantics, built-in workflow behavior, and workflow-step templates; read [Workflow Editor](./docs/workflow-editor.md) for the dashboard authoring guide.
|
||||
|
||||
<!--
|
||||
FNXC:PlannerOversight 2026-07-05-00:00:
|
||||
The planner-oversight feature (FN-7508 → FN-7583) shipped fully documented in internal/reference docs (architecture.md, settings-reference.md, dashboard-guide.md, workflow-steps.md) but had no front-door entry point in README.md, so a new operator could not discover it. Add a short overview here that links out to the canonical docs instead of duplicating engine internals (FN-7598).
|
||||
-->
|
||||
|
||||
### Planner oversight
|
||||
|
||||
Each workflow (and optionally each task) can set a **planner oversight** level — `off`, `observe`, `steer`, or `autonomous` (default) — controlling how closely a planner overseer watches and intervenes in that task's execution. Even at `autonomous`, merge/PR progression and any destructive or external-service side effect always require an explicit, recorded human confirmation before they run. Notification verbosity is controlled separately. Set the default in the **Workflow Editor → Values** tab, or override per task from the New Task dialog / Task Detail edit form. Read [Settings Reference](./docs/settings-reference.md#workflow-settings) for the full setting semantics and [Dashboard Guide](./docs/dashboard-guide.md) for the UI controls.
|
||||
|
||||
---
|
||||
|
||||
## Multi-node. One board. Every platform.
|
||||
@@ -504,6 +514,7 @@ npx companies.sh add paperclipai/companies/gstack
|
||||
- **Visual Workflow Editor** — Inspect read-only built-ins, duplicate/customize workflows, and edit graph nodes, columns, task fields, typed settings, and per-project values ([Workflow Editor](./docs/workflow-editor.md))
|
||||
- **Workflow Steps** — Configurable quality gates (pre-merge: blocks merge; post-merge: informational), plus workflow-declared optional steps such as opt-in [Browser Verification](./docs/workflow-steps.md#workflow-declared-optional-steps)
|
||||
- **Workflow-native policy** — Fast-mode planning (`leanPlanning` / `autoApproveSpec`), typed triage thresholds, review/approval, step execution, and model/fallback lanes are workflow settings, not hard-coded engine constants ([Settings Reference](./docs/settings-reference.md#workflow-native-triage-policy-settings); [workflow settings](./docs/settings-reference.md#workflow-settings))
|
||||
- **Planner oversight** — Workflow-native `plannerOversightLevel` (`off`/`observe`/`steer`/`autonomous`), with an optional per-task override and a separate notification-verbosity setting; merge/PR progression and destructive actions always require explicit human confirmation, even at `autonomous` ([overview](#planner-oversight); [Settings Reference](./docs/settings-reference.md#workflow-settings))
|
||||
- **GitHub + PR lifecycle** — Import issues, create PRs, display real-time PR/issue badges, and use workflow-mode PR lifecycle graph fragments where enabled
|
||||
- **Dashboard** — Real-time kanban/list/graph views, agent management, terminal, git manager, mission planner, chat, workflow editor, custom provider setup, and one-click update action
|
||||
- **Missions** — Hierarchical planning (Mission → Milestone → Slice → Feature → Task) with autopilot, validation contracts, fix-feature retries, mission-goal linking, and blocked-handoff semantics
|
||||
|
||||
@@ -43,11 +43,14 @@ For a full walkthrough (installation, onboarding, first task, and daily workflow
|
||||
| [Multi-Project](./multi-project.md) | Central registry architecture, project management, isolation modes, and migration paths |
|
||||
|
||||
### Configuration & Agents
|
||||
| Guide | Description |
|
||||
|---|---|
|
||||
<!--
|
||||
FNXC:DocsIndex 2026-07-05-00:00:
|
||||
Planner oversight (FN-7508 → FN-7583) is fully documented in Settings Reference, Dashboard Guide, and Architecture but had no index entry, so it was undiscoverable from the docs hub. Add a labeled pointer row rather than a new doc, since the existing reference docs already cover the feature end to end (FN-7598).
|
||||
-->
|
||||
| [Settings Reference](./settings-reference.md) | Global/project settings, workflow setting values, model/fallback lane hierarchy, defaults, and API endpoints |
|
||||
| [MCP](./mcp.md) | Model Context Protocol server configuration, secret references, validation, CLI, dashboard, and import/export workflows |
|
||||
| [Agents](./agents.md) | Agent management, presets, prompts, heartbeat behavior, spawning, and mailbox workflows |
|
||||
| Planner Oversight (see [Settings Reference](./settings-reference.md#workflow-settings), [Dashboard Guide](./dashboard-guide.md), [Architecture](./architecture.md)) | Workflow-native oversight levels (`off`/`observe`/`steer`/`autonomous`), per-task overrides, notification verbosity, the human-confirmation gate on merge/PR and destructive actions, and the Task Detail overseer controls/Intervention Timeline |
|
||||
|
||||
### Architecture & Development
|
||||
| Guide | Description |
|
||||
@@ -151,5 +154,5 @@ FN-7088 links previously-unlinked first-class testing and baseline docs here so
|
||||
|
||||
- **New user:** Getting Started → Dashboard Guide → Task Management
|
||||
- **Workflow author:** Dashboard Guide → Workflow Editor → Workflow Steps → Settings Reference
|
||||
- **Power user / automation owner:** Settings Reference → Workflow Steps → Agents
|
||||
- **Power user / automation owner:** Settings Reference → Workflow Steps → Agents → Planner Oversight (Settings Reference § Workflow Settings)
|
||||
- **Maintainer / contributor:** Architecture → Multi-Project → Contributing
|
||||
|
||||
@@ -196,14 +196,18 @@ Features:
|
||||
<!-- FNXC:PlannerOversight 2026-07-04-19:10: FN-7539 fix — the badge was rendering on virtually every card because the schema default `autonomous` tier was treated as "meaningfully configured". Narrowed the gate so an inherited (no per-task-override, no non-default workflow tier) `autonomous` level renders no badge; only an explicit per-task override or a resolved workflow/effective tier that is not the plain inherited default surfaces the badge. -->
|
||||
- Task cards show a read-only **oversight-level badge** (`Observe`, `Steer`, or `Auto-recovery`) in the meta-badges cluster reflecting the effective planner-oversight level, but only when oversight is *meaningfully configured* — an explicit per-task override (including an explicit `autonomous` override), or a resolved workflow/effective tier of `observe`/`steer` (`data-testid="card-oversight-badge"`). A card that merely **inherits** the schema default `autonomous` tier (no per-task override, no non-default workflow tier) renders no badge and no empty `.card-meta-badges` shell. The badge is also absent when the effective level is explicitly "off", **and** while an inherited (no per-task-override) workflow tier is still being resolved (in flight or not yet fetched) — it never shows a guessed default during that window.
|
||||
<!-- FNXC:PlannerOversight 2026-07-04-HH:MM: FN-7542 removed the FN-7516 active-overseer-state ("Executor") indicator described above as unwanted per-card noise — it fired on nearly every in-progress card. The oversight-level badge documented above is unaffected. -->
|
||||
<!-- FNXC:PlannerOversight 2026-07-04-17:00: FN-7517 adds interactive task-detail planner-overseer controls (quick level change, manual nudge, stop oversight, explain current action) alongside the FN-7516 read-only card badges above. These controls live ONLY in TaskDetailModal, not TaskCard. -->
|
||||
- The task detail modal's inline meta-controls cluster (next to Priority/Execution mode) adds four planner-overseer controls: a **quick oversight-level select** (`data-testid="detail-oversight-level-select"`) that writes the per-task `plannerOversightLevel` override (Off/Observe/Steer/Autonomous recovery) or clears it back to the inherited workflow/project default via an "Inherit" option; a **manual nudge** button (`data-testid="detail-overseer-nudge"`) that asks the overseer to inject one guidance-only steering comment into the currently watched stage right now (never a merge/PR/destructive action), disabled when the overseer is off/inactive or the task is user-paused/done/archived/`autoMerge:false` in-review; a **stop oversight** button (`data-testid="detail-overseer-stop"`) that disables active oversight for the task (confirmation-gated), hidden once oversight is already off; and an **explain current action** button (`data-testid="detail-overseer-explain"`) that toggles a small read-only panel (`data-testid="detail-overseer-explain-panel"`) showing the overseer's watched stage, reason, last action, and attempt count/limit, with a non-empty-shell inactive state when the overseer is not currently watching. All three action controls call the `POST /tasks/:id/overseer/nudge`, `POST /tasks/:id/overseer/stop`, and `GET /tasks/:id/overseer/explain` routes.
|
||||
<!-- FNXC:PlannerOversight 2026-07-04-17:00: FN-7517 adds interactive task-detail planner-overseer controls (quick level change, manual nudge, stop oversight, explain current action) alongside the FN-7516 read-only card badges above. These controls live ONLY in TaskDetailModal, not TaskCard.
|
||||
|
||||
FNXC:PlannerOversight 2026-07-05-00:00: FN-7604 collapses the desktop inline cluster (documented below through FN-7545/FN-7546) into the single universal overflow-menu dropdown that FN-7545 originally built for mobile only — the dropdown is now the ONE canonical surface on every viewport, desktop included. -->
|
||||
- The task detail modal's inline meta-controls cluster (next to Priority/Execution mode) exposes planner-overseer controls behind a single compact **"Oversight" overflow-menu button** (`data-testid="detail-oversight-menu-trigger"`, `MoreVertical` icon, `aria-haspopup="menu"`) on every viewport, desktop and mobile alike. Clicking the trigger opens a `role="menu"` popover (mirroring the existing move-action dropdown pattern) containing: a **quick oversight-level select** (`data-testid="detail-oversight-level-select"`) that writes the per-task `plannerOversightLevel` override (Off/Observe/Steer/Autonomous recovery) or clears it back to the inherited workflow/project default via an "Inherit" option; a **manual nudge** button (`data-testid="detail-overseer-nudge"`) that asks the overseer to inject one guidance-only steering comment into the currently watched stage right now (never a merge/PR/destructive action), disabled when the overseer is off/inactive or the task is user-paused/done/archived/`autoMerge:false` in-review; a **stop oversight** button (`data-testid="detail-overseer-stop"`) that disables active oversight for the task (confirmation-gated), hidden once oversight is already off; and an **explain current action** button (`data-testid="detail-overseer-explain"`) that toggles a small read-only panel (`data-testid="detail-overseer-explain-panel"`) showing the overseer's watched stage, reason, last action, and attempt count/limit, with a non-empty-shell inactive state when the overseer is not currently watching. All three action controls call the `POST /tasks/:id/overseer/nudge`, `POST /tasks/:id/overseer/stop`, and `GET /tasks/:id/overseer/explain` routes. The trigger itself is withheld entirely when none of the menu's controls would render (no empty-shell overflow button for the unresolved-default case).
|
||||
<!-- FNXC:PlannerOversight 2026-07-04-20:30: FN-7546 clarifies the cluster above — operators reported the buttons were unlabeled and looked inert, with only a hover title explaining why. Adds a visible group label and an always-visible disabled-reason line, and makes Explain always openable since it never mutates anything. -->
|
||||
- The FN-7517 controls above carry a visible, non-interactive **`"Overseer controls"` group label** (`data-testid="detail-oversight-controls-label"`) so Nudge/Stop/Explain read as an identifiable cluster rather than unlabeled chips; the label is gated by the same `(hasTaskOversightOverride || workflowOversightResolved) && !oversightIsOff` condition as the buttons, so it never renders an empty shell when oversight is Off/unresolved. When **Nudge** is disabled, an always-visible helper line (`data-testid="detail-overseer-nudge-disabled-reason"`) states the reason in-DOM (mirroring the existing hover `title`) instead of relying on a mouse-hover tooltip alone. **Explain** is read-only and non-mutating, so it is never disabled purely because the overseer is inactive — clicking it always opens/closes the panel, which shows the overseer's live state when watching or an informative "not currently watching this task" message otherwise. Nudge's mutating enablement rule (`canNudgeOverseer`, including the human-control suppression cases) and Stop's confirmation dialog are unchanged.
|
||||
<!-- FNXC:PlannerOversight 2026-07-04-19:00: FN-7545 collapses the above controls into a mobile overflow menu below the 768px breakpoint. -->
|
||||
- On mobile (viewport ≤ 768px), the level-select/nudge/stop/explain controls above collapse behind a single compact **"Oversight" overflow-menu button** (`data-testid="detail-oversight-menu-trigger"`, `MoreVertical` icon, `aria-haspopup="menu"`) that opens a `role="menu"` popover (mirroring the existing move-action dropdown pattern) listing the same controls as full-width, comfortably tappable `role="menuitem"` entries with the SAME testids and enablement/visibility rules as the desktop inline controls. The trigger itself is withheld entirely when none of those controls would render (no empty-shell overflow button for the oversight-off + overseer-inactive default). Desktop (>768px) keeps the inline cluster unchanged.
|
||||
<!-- FNXC:PlannerOversight 2026-07-04-18:00: FN-7519 adds a read-only Intervention Timeline immediately below the FN-7517 controls above. -->
|
||||
- Below the quick oversight controls, the task detail modal renders an **Intervention Timeline** (`data-testid="planner-intervention-timeline"`) listing every recorded planner-overseer intervention for the task, newest-first: watched stage, reason, action taken, outcome (with a `.status-dot` indicator using semantic outcome tokens), an attempt count/limit badge (only when both are present), and source links (agent log / review comment / failed check / merge error / PR state / generic URL). It renders a calm "No planner interventions yet" empty state rather than an empty shell when there are none, and is hidden entirely (no leftover container) when oversight is off or unresolved for the task. Entries are read via `GET /tasks/:id/overseer/interventions`, which assembles them from the existing run-audit store under the `overseer:intervention` mutation type (`recordPlannerIntervention`/`getPlannerInterventionTimeline` in `@fusion/core`). This is a pure read surface — FN-7520 wires the actual intervention-producing call-sites.
|
||||
- The controls inside the Oversight menu carry a visible, non-interactive **`"Overseer controls"` group label** (`data-testid="detail-oversight-controls-label"`) so Nudge/Stop/Explain read as an identifiable cluster rather than unlabeled entries; the label is gated by the same `(hasTaskOversightOverride || workflowOversightResolved) && !oversightIsOff` condition as the buttons, so it never renders when oversight is Off/unresolved (opening the menu in that state shows only the level select). When **Nudge** is disabled, an always-visible helper line (`data-testid="detail-overseer-nudge-disabled-reason"`) states the reason in-DOM (mirroring the existing hover `title`) instead of relying on a mouse-hover tooltip alone. **Explain** is read-only and non-mutating, so it is never disabled purely because the overseer is inactive — clicking it always opens/closes the panel, which shows the overseer's live state when watching or an informative "not currently watching this task" message otherwise. Nudge's mutating enablement rule (`canNudgeOverseer`, including the human-control suppression cases) and Stop's confirmation dialog are unchanged.
|
||||
<!-- FNXC:PlannerOversight 2026-07-04-19:00: FN-7545 originally introduced this overflow menu for mobile only, collapsing the level-select/nudge/stop/explain controls behind the trigger below the 768px breakpoint while desktop kept an inline cluster.
|
||||
|
||||
FNXC:PlannerOversight 2026-07-05-00:00: FN-7604 removed that desktop-inline branch. The overflow-menu trigger + popover described above is now the SINGLE canonical surface at EVERY viewport — there is no longer a desktop/mobile split. -->
|
||||
- The Oversight overflow-menu trigger and its popover render identically at every viewport (desktop and mobile): the same testids, the same enablement/visibility rules, and the same `role="menu"`/`role="menuitem"` popover semantics. Menu-open auto-focus lands on the first actionable button menuitem, never the native level `<select>`, so the OS option picker never auto-opens on top of the custom popover (FN-7562).
|
||||
<!-- FNXC:PlannerOversight 2026-07-04-18:00: FN-7519 adds a read-only Intervention Timeline. FN-7571 (2026-07-04-19:00) relocates it from an inline mount below the FN-7517 controls into the task-detail Activity view dropdown as a fourth "Interventions" segment, alongside Live/Feed/Raw. -->
|
||||
- The task detail modal's **Activity** tab view dropdown (Live/Feed/Raw) gains a fourth **Interventions** option, shown only when planner oversight is active for the task (same gate as the former inline mount: `(hasTaskOversightOverride || workflowOversightResolved) && !oversightIsOff`). Selecting it renders the **Intervention Timeline** (`data-testid="planner-intervention-timeline"`) inside the Activity panel, listing every recorded planner-overseer intervention for the task, newest-first: watched stage, reason, action taken, outcome (with a `.status-dot` indicator using semantic outcome tokens), an attempt count/limit badge (only when both are present), and source links (agent log / review comment / failed check / merge error / PR state / generic URL). It renders a calm "No planner interventions yet" empty state rather than an empty shell when there are none. When oversight is off or unresolved, the Interventions option is absent from the dropdown entirely (no leftover empty segment), and if it was previously selected the view falls back to Live rather than leaving a blank panel. Entries are read via `GET /tasks/:id/overseer/interventions`, which assembles them from the existing run-audit store under the `overseer:intervention` mutation type (`recordPlannerIntervention`/`getPlannerInterventionTimeline` in `@fusion/core`). This is a pure read surface — FN-7520 wires the actual intervention-producing call-sites.
|
||||
- Task detail surfaces show the selected/effective workflow identity near the task's workflow controls so individual cards remain understandable when Board is in **All workflows** or another aggregate/mixed context.
|
||||
- Board task cards support a context menu from right-click, keyboard context menu / Shift+F10, or touch long-press for detail-aligned lifecycle actions without changing normal card clicks. The menu opens as an independent overlay so it stays visible beyond the card or column edge while remaining clamped to the viewport. On mobile, long-press opens that menu without selecting card text or showing native copy/paste callouts. Selecting an action applies that exact action once and dismisses the menu. Completed card context menus include **Refine**, which opens the existing task-detail refinement feedback modal for the same task.
|
||||
<!-- FNXC:BoardCardActions 2026-06-29-00:00: Board card context menus are documented as alternate entry points only; normal click still opens task detail, and mobile long-press must not trigger detail behind the menu.
|
||||
@@ -295,7 +299,7 @@ Behavior:
|
||||
- Renders directed bezier dependency edges (dependent → dependency) with arrowheads
|
||||
- Supports cursor-centered wheel zoom, pinch zoom, keyboard shortcuts (`Ctrl/Cmd+=`, `Ctrl/Cmd+-`, `Ctrl/Cmd+0`, `Ctrl/Cmd+Shift+F`, `Escape`), and fit/reset controls via the floating toolbar with live zoom percentage
|
||||
- Pan limits are zoom-aware and based on full graph extents (including negative auto-layout origins), so zoomed-in views can still pan to every rendered node instead of getting trapped by fixed viewport-only bounds
|
||||
- Dependency graph nodes reuse the same `TaskCard` UI as board/list views, so status badges, progress/steps, mission badges, retry/archive controls, and active-task glow stay visually consistent
|
||||
- Dependency graph nodes reuse the same `TaskCard` UI as board/list views, so status badges, progress/steps, mission badges, retry/archive/revert controls, and active-task glow stay visually consistent
|
||||
- Active graph nodes also add a dedicated top status indicator bar and current-step row highlighting so in-progress execution state stays visible even when zoomed out
|
||||
- Clicking a graph card opens task details in the shared movable/resizable task pop-out via the host detail handler (`onOpenDetail`, with `onOpenTaskDetail` fallback), while clicking the same card again or empty canvas clears selection.
|
||||
- On touch devices, single-tap is reserved for pan/drag gestures, so double-tapping a node opens the same shared task pop-out; this does not change selection state.
|
||||
@@ -432,6 +436,10 @@ When quick-create task creation, Planning Mode, or Subtask Breakdown runs from a
|
||||
|
||||
The **New Task** dialog's workflow selector also defaults to the current or last selected Board/List workflow lane for the current project. If no valid lane has been selected, or the remembered lane was deleted, the selector falls back to the project default workflow and task creation omits an explicit `workflowId`.
|
||||
|
||||
<!-- FNXC:CodingIdeasWorkflow 2026-07-05-00:00: Every dashboard create surface (inline quick-create, quick-add box, New Task dialog, insight → task, todo → task) must omit an explicit `column` from the create request so the store resolves the landing column from the (selected or project-default) workflow's intake column instead of always forcing legacy `triage`. -->
|
||||
|
||||
Create requests never send an explicit `column`. The task store resolves the landing column from the (selected or project-default) workflow's intake column, so most tasks still land in `triage` under the default Coding workflow, byte-identical to before. A workflow with a **manual intake column** — for example the built-in **Coding (Ideas)** workflow's `ideas` column (`autoTriage: false`) — parks new cards there instead: they wait for you to promote them into `todo` and are not auto-planned by the triage service until you do.
|
||||
|
||||
Optional workflow steps declared by the active workflow are available from the quick-add action row and the **New Task** dialog's inline quick buttons. For example, the coding workflow's browser verification option appears as a quick drop-down when that workflow is active; each option is seeded from the workflow step's `defaultOn` setting and is sent with the task's `enabledWorkflowSteps` payload at creation time.
|
||||
|
||||
<!-- FNXC:QuickAddAttachments 2026-06-30-00:00: Quick Add attachments use a compact icon-only paperclip while keeping the Attach action and pending image count in accessible labels. The same pending preview/upload path accepts image selection, paste, and direct drag/drop onto the Quick Add box. -->
|
||||
@@ -705,6 +713,10 @@ For Claude/Anthropic OAuth credentials, the same `/auth/status` poll also attemp
|
||||
|
||||
If the OAuth credential has no refresh token, the refresh request fails, or the provider is not Anthropic, the provider stays expired and the banner remains visible. Re-authenticate with manual re-login from **Settings → Authentication** or Model Onboarding. On Fusion desktop, OAuth login URLs open in the operating system browser rather than an in-app Electron child window; the Settings/Onboarding UI keeps polling until the provider authenticates or the login truly stops.
|
||||
|
||||
<!-- FNXC:ProviderAuth 2026-07-05-00:00: FN-7574 — the status route and the engine's OAuthExpiryMonitor previously diverged: a subscription OAuth credential with a past or missing/non-numeric `expires` could still read authenticated:true from /api/auth/status even though the monitor had already fired an oauth-token-expired notification for it. `/api/auth/status` now treats any OAuth credential lacking a usable numeric `expires` — and any credential whose numeric `expires` is in the past and cannot be refreshed — as expired:true/authenticated:false, for both the legacy anthropic-row and separated anthropic-subscription-row storage permutations. Settings → Authentication and the global re-login banner both read this corrected status, so an expired-and-unrefreshable subscription now consistently shows as not connected everywhere. -->
|
||||
|
||||
<!-- FNXC:ClaudeOAuth 2026-07-05-00:00: FN-7574 — beyond the reactive best-effort refresh on the /auth/status poll, the engine now runs an independent background OAuthRefreshScheduler (packages/engine/src/notification/oauth-refresh-scheduler.ts) on a 5-minute interval, guarded by the same `skipNotifier` option as OAuthExpiryMonitor. It proactively calls the existing refresh-if-due logic in auth-storage.ts for every known OAuth provider (plus the anthropic-subscription alias) so a healthy subscription's access token is renewed well ahead of expiry via the stored refresh token, instead of only refreshing reactively when something happens to request a runtime API key. Only providerId/providerName/expiresAt are ever logged — never token material. -->
|
||||
|
||||
Anthropic also supports a raw `ANTHROPIC_API_KEY` from a separate **Anthropic API Key** card in **Settings → Authentication** and Model Onboarding. Claude subscription OAuth remains on the **Anthropic Subscription** card for auth status, usage/subscription checks, and banner clearing; it also drives direct agent execution on the `anthropic` provider — a subscription/OAuth token runs `anthropic/*` selections against `https://api.anthropic.com/v1` with Claude Code identity headers, no API key required. CLI-backed execution remains the distinct, explicit **Claude CLI** provider (`pi-claude-cli`); subscription OAuth does not require it. When Anthropic Subscription is expired but Anthropic API Key or Anthropic — via Claude CLI is already authenticated, the global banner suppresses only the urgent subscription re-login entry so it does not imply agents are blocked; Settings still shows the subscription OAuth card as expired/not connected and re-login remains available. A configured API key takes precedence over OAuth on the direct provider. Saving or clearing an API key does not affect the OAuth sign-in path or turn OAuth tokens into raw API-key material. The dashboard only displays masked key hints after a key is saved.
|
||||
|
||||
## Setup Warning Banner
|
||||
|
||||
@@ -137,6 +137,9 @@ Most tasks can use the default **Coding** workflow. When the workflow selector i
|
||||
|
||||
Built-ins include task-selectable Coding, Legacy coding, Quick fix, Review-heavy, plugin-gated Compound engineering, Coding (per-step review), and Design workflows, plus PR lifecycle fragments for workflow authors. For the full catalog and runtime behavior, see [Workflow Steps](./workflow-steps.md#workflow-overview). To inspect built-ins or author custom workflows, open the dashboard [Workflow Editor](./workflow-editor.md).
|
||||
|
||||
<!-- FNXC:PlannerOversight 2026-07-05-00:00: shipped planner-oversight feature (FN-7508 → FN-7583) had no getting-started pointer, so new operators could not discover per-task/workflow oversight controls; add a one-line pointer to the reference docs (FN-7598). -->
|
||||
Workflows (and individual tasks) also have a **Planner oversight** level (`off`/`observe`/`steer`/`autonomous`) that controls how closely a planner overseer watches and can intervene; see [Settings Reference](./settings-reference.md#workflow-settings) for the setting semantics and [Dashboard Guide](./dashboard-guide.md) for the UI controls.
|
||||
|
||||
## Understand the Task Lifecycle
|
||||
|
||||
Fusion uses six default lifecycle columns:
|
||||
|
||||
@@ -41,7 +41,7 @@ Required included surfaces: issue import, linked issue tracking, completion comm
|
||||
| Post-create tracking hook | `registerGithubTrackingHook()` registers a universal post-create hook for dashboard, CLI, extension, mission, automation, delegation, routine, duplicate/refine, and subtask creation paths. | `github-tracking-hook.ts`; `docs/task-management.md` | GitLab tracking must register an equivalent universal post-create hook at the same process entrypoints, or a provider-neutral hook dispatcher that calls GitHub/GitLab handlers. It must be best-effort and non-blocking. |
|
||||
| Tracking title/body format | GitHub tracking issues use title `[FN-XXXX] Task title` and body prefix `Fusion task: FN-XXXX` with bounded plain-text summary. | `github-tracking.ts`; docs contract test | GitLab tracking issues should use the same title/body invariant unless later UX explicitly changes it. Keep body bounded and do not include local dashboard links. |
|
||||
| Tracking lifecycle comments | GitHub posts lifecycle comments on tracked issues for in-progress/done, with bounded plain text and optional merge metadata. | `github-tracking-comments.ts` | GitLab should post issue notes for the same lifecycle moments to linked GitLab tracking issues. Merge-request notes are only relevant for imported MR review tasks and must be designed separately. |
|
||||
| Completion comments on source issues | `githubCommentOnDone` and optional `githubCommentTemplate` post a completion comment to imported GitHub source issues when tasks enter done. | `github-issue-comment.ts`; settings | Add GitLab equivalents using project issue notes. The template placeholders should remain provider-neutral (`{taskId}`, `{taskTitle}`) or be explicitly duplicated with `gitlabCommentOnDone`/`gitlabCommentTemplate`. Group issue imports still comment through owning project issue notes. |
|
||||
| Completion comments on source issues | `githubCommentOnDone` and optional `githubCommentTemplate` post a completion comment to imported GitHub source issues when tasks enter done. On GitHub, when the linked source issue's repository is the Fusion self-repo (`runfusion/fusion`, case-insensitive), the comment additionally appends `Current version:` and `Target release:` lines (next-minor bump of the resolved `@runfusion/fusion` CLI package version); this enrichment is GitHub/Fusion-specific and intentionally out of scope for this GitLab parity pass. | `github-issue-comment.ts`; settings | Add GitLab equivalents using project issue notes. The template placeholders should remain provider-neutral (`{taskId}`, `{taskTitle}`) or be explicitly duplicated with `gitlabCommentOnDone`/`gitlabCommentTemplate`. Group issue imports still comment through owning project issue notes. A GitLab release-version enrichment equivalent is not implemented here — file a follow-up if needed. |
|
||||
| Auto-close imported source issues | `githubCloseSourceIssueOnDone` closes/reopens source-imported GitHub issues on task state transitions and startup reconciliation closes missed done tasks. | `github-source-issue-close.ts`; `github-tracking-reconciler.ts` | Add GitLab source issue close/reopen on imported project issues. Reconciliation must use stored GitLab project identity plus issue IID/global ID. Group issue rows cannot be closed through group endpoint; resolve owning project first. Do not auto-close or merge merge requests unless a later task explicitly adds an MR policy. |
|
||||
| Source issue close timestamp backfill | Command Center GitHub fixed counts can be made exact by `POST /api/git/github/backfill-source-issue-closed-at` in manual batches. | `GithubArea.tsx`; `register-git-github.ts`; `TaskSourceIssue.closedAt` | Add a GitLab manual backfill for imported GitLab source issues/MRs if analytics need exact close/merged timestamps. It must be an explicit operator action and never run during render-time analytics. |
|
||||
| Webhooks for linked state | `POST /api/github/webhooks` verifies GitHub App signatures and updates issue/PR/task badges from pull_request/issues/issue_comment events. | `github-webhooks.ts`; `register-git-github.ts` | GitLab webhook support should use GitLab project/group/system hooks as provider-specific signed signal ingestion. Badge/state update hooks for GitLab-linked issues/MRs are separate from Command Center Signals but can reuse signature-validation patterns. Document exact GitLab headers/secrets in the implementation task. |
|
||||
|
||||
@@ -412,6 +412,7 @@ Security-sensitive file-browser escape hatches are project-only. `allowAbsoluteF
|
||||
| `secretsEnv` | `{ enabled?: boolean; filename?: string; overwritePolicy?: "skip" \| "merge" \| "replace"; keyPrefix?: string; requireGitignored?: boolean }` | `undefined` | Per-project secrets `.env` materialization configuration. When `enabled`, the engine writes `secretsEnv.filename` (default `.env`) into each acquired task worktree from secrets marked `env_exportable=true`. `overwritePolicy` controls merge/skip/replace against an existing file; `requireGitignored` (default `true`) refuses to write a non-gitignored path; `keyPrefix` filters which exported keys are included. See [Secrets](./secrets.md#env-auto-write-into-worktrees). |
|
||||
| `mcpServers` | `McpServersSettings` | `{ enabled: false, servers: [] }` | Project-scoped MCP server settings. Project entries override global entries by `name`; `enabled:false` on a same-named project entry disables the inherited global server. Sensitive env/header/token material must be Fusion secret references only. See [MCP server settings](#mcp-server-settings). |
|
||||
| `owningNodeHandoffPolicy` | `"block" \| "reassign-to-local" \| "reassign-any-healthy"` | `"reassign-to-local"` | Policy for tasks already checked out by an unavailable owning node. `"block"` parks, `"reassign-to-local"` takes over on local node, `"reassign-any-healthy"` makes takeover eligible on healthy peers. |
|
||||
| `aiUndoTaskWorkflowId` | `string` | `"builtin:review-heavy"` | **FN-7556 / FN-7578.** Workflow selected for AI-undo board tasks created by `POST /api/tasks/:id/revert` (`mode: "ai"` and the `auto`/workspace conflict fallbacks) — a stricter review posture since these tasks reverse already-shipped code. Blank/unset means the created task inherits the project default workflow. The route validates the configured id and falls back to inherit on a blank or unknown value, so a misconfigured id never breaks AI-undo task creation. Editable from **Settings → General** ("AI-undo task workflow" picker, next to the workflow-enablement controls); choose "Inherit project default workflow" to store the blank/inherit sentinel. See [Task Management → Reverting Done/Archived tasks](./task-management.md#reverting-donearchived-tasks-git-path--ai-undo-fallback). |
|
||||
|
||||
| `groupOverlappingFiles` | `boolean` | `true` | Serialize execution when file scopes overlap. |
|
||||
| `pluginTrustPolicy` | `"off" | "warn" | "enforce"` | `"warn"` | Plugin provenance enforcement mode: `off` records verification metadata only, `warn` blocks only `invalid` signatures, `enforce` allows only `verified-trusted` or `trusted-local`. |
|
||||
@@ -419,7 +420,7 @@ Security-sensitive file-browser escape hatches are project-only. `allowAbsoluteF
|
||||
| `overlapIgnorePaths` | `string[]` | `[]` | Optional project-relative file or directory paths to exclude from overlap blocking (for example `docs` or `generated/openapi.json`). Entries are trimmed, deduplicated, and must not be absolute or contain `..` traversal. |
|
||||
| `allowAbsoluteFileBrowserPaths` | `boolean` | `false` | Project-scoped Settings → General toggle for the workspace file browser. When enabled, slash-prefixed paths such as `/tmp` can be listed/read/written/downloaded through workspace file-browser routes while keeping existing file-size, binary, type, null-byte, traversal, and permission checks. Windows drive-letter paths remain blocked, and task-local file routes, memory APIs, worktree-copy validation, plugin bundle paths, and other validators are unchanged. |
|
||||
| `autoMerge` | `boolean` | `true` | Auto-finalize tasks from `in-review`. Tasks can override this per-task (including at create time in New Task modal via **Auto-merge** = Default/Enabled/Disabled); explicit overrides are tagged with `autoMergeProvenance: "user"`, while tasks left at **Default** keep following the live global setting and do not snapshot it when entering review. Legacy pre-FN-6245 in-review rows that were stamped `autoMerge: true` are marked `autoMergeProvenance: "legacy-stamp"` on startup and can be inspected/cleared with Settings → Merge → **Legacy auto-merge stamp cleanup**, `fn pr automerge-cleanup [--apply] [--json]`, or `reconcileLegacyAutoMergeStamps({ apply: true })` after operator review. For grouped branch flows, per-task `autoMerge` governs member→group-integration landing while group `autoMerge` governs group→default-branch promotion eligibility. |
|
||||
| `planApprovalMode` | `"workflow" \| "auto-approve-all" \| "require-all"` | `"auto-approve-all"` | Project-scoped override for the manual planning approval gate. Defaults to auto-approve-all (FN-7557) so new/unset projects skip the manual gate; `"workflow"` instead preserves the workflow-resolved `requirePlanApproval`; `"auto-approve-all"` moves every successfully specified task to `todo` without manual plan approval even when the selected workflow or stored workflow setting has `requirePlanApproval: true`; `"require-all"` parks every specified task at `status: "awaiting-approval"` regardless of workflow settings. Settings → Merge remains the full three-state editor; the Board Triage/intake **Auto-approve plan** switch is a binary shortcut for `"auto-approve-all"` vs `"workflow"`. This does not disable Workflow Plan Review, release authorization, or other non-plan safety gates. |
|
||||
| `planApprovalMode` | `"workflow" \| "auto-approve-all" \| "require-all"` | `"auto-approve-all"` | Project-scoped override for the manual planning approval gate. Defaults to auto-approve-all (FN-7557) so new/unset projects skip the manual gate; `"workflow"` instead preserves the workflow-resolved `requirePlanApproval`; `"auto-approve-all"` moves every successfully specified task to `todo` without manual plan approval even when the selected workflow or stored workflow setting has `requirePlanApproval: true`; `"require-all"` parks every specified task at `status: "awaiting-approval"` regardless of workflow settings. Settings → Merge remains the full three-state editor; the Board Triage/intake **Auto-approve plan** switch is a binary shortcut for `"auto-approve-all"` vs `"workflow"`. This does not disable Workflow Plan Review, release authorization, or other non-plan safety gates. **FN-7559:** release authorization and the manual gate both use `status: "awaiting-approval"`, so a release-class task that still parks under `"auto-approve-all"` is NOT a broken auto-approve — it is the intentionally-not-bypassed release-authorization gate. The task carries `awaitingApprovalReason: "release-authorization"` in that case (undefined for a genuine manual hold), and the dashboard renders a distinct "Awaiting Release Authorization" label with the Approve/Reject Plan buttons hidden, instead of the generic manual-approval affordance. **FN-7569:** approving a plan under `"workflow"`/`"require-all"` manual approval records a fingerprint (hash) of the exact approved `PROMPT.md`. If the task is later re-specified (a replan, a plan-review reviewer-outage retry, or a self-healing rebound back to triage) and produces the identical plan content, the manual gate is idempotent: it skips re-parking at `status: "awaiting-approval"` and proceeds straight to `todo`, so the operator is never asked to re-approve a plan they already approved. A genuinely changed `PROMPT.md` still re-asks, and rejecting a plan (Reject Plan) clears the fingerprint so the regenerated plan is treated as new. This idempotency check lives strictly inside the manual gate, after release authorization and Workflow Plan Review have already decided, and has no effect under `"auto-approve-all"` (which never reaches the manual gate). |
|
||||
| `maxAutoMergeRetries` | `number` | `3` | Project-scoped positive-integer cap for auto-merge conflict-resolution retries before Fusion parks or bounces a task for human/recovery handling. Unset, non-finite, zero, or negative values fall back to `3` to preserve historical behavior. |
|
||||
| `mergeRequestContractShadowEnabled` | `boolean` | `false` | Phase-1 FN-5741 write-only shadow flag (project/global setting). When enabled, executor/self-healing/merger persist merge-request records and `completion_handoff_accepted` markers for observation only; legacy mergeQueue + lifecycle remains authoritative. |
|
||||
| `mergeStrategy` | `"direct" \| "pull-request"` | `"direct"` | Completion mode (local direct merge vs PR-first). |
|
||||
@@ -603,7 +604,7 @@ Default notes:
|
||||
| `archiveAgentLogMode` | `"none" \| "compact" \| "full"` | `"compact"` | Agent log retention strategy for cold archive snapshots. |
|
||||
| `autoUpdatePrStatus` | `boolean` | `false` | Auto-refresh PR status badges. |
|
||||
| `githubCommentOnDone` | `boolean` | `false` | When enabled, tasks imported from GitHub issues post a completion comment to the source issue when the task moves to `done`. |
|
||||
| `githubCommentTemplate` | `string` | `undefined` | Optional issue comment template used by `githubCommentOnDone`. Supports `{taskId}` and `{taskTitle}` placeholders. If unset, Fusion uses a default completion message. |
|
||||
| `githubCommentTemplate` | `string` | `undefined` | Optional issue comment template used by `githubCommentOnDone`. Supports `{taskId}` and `{taskTitle}` placeholders. If unset, Fusion uses a default completion message. When the linked source issue's repository is the Fusion self-repo (`runfusion/fusion`, case-insensitive), Fusion appends a `Current version: v<current>` line and a `Target release: v<nextMinor>` line (next-minor bump, patch reset to 0, e.g. `0.55.0` → `0.56.0`), resolved via the published `@runfusion/fusion` CLI package version. If that version is unresolved/unparseable, the base comment is posted with no version lines. Comments on every other repository are byte-for-byte unchanged. |
|
||||
| `githubCloseSourceIssueOnDone` | `boolean` | `false` | When enabled, source-imported GitHub issues are automatically closed with `state_reason: completed` when the Fusion task moves to `done`. A startup reconciliation sweep also closes missed open source issues on boot. |
|
||||
| `githubTrackingEnabledByDefault` | `boolean` | `false` | Project-level default for enabling issue tracking on ordinary new tasks. When this is false, the Quick Entry GitHub toggle is disabled until tracking is enabled in Settings. Imported GitHub issues still follow this default unless `githubLinkImportedIssuesToTracking` is enabled. |
|
||||
| `githubLinkImportedIssuesToTracking` | `boolean` | `false` | Project-scoped, import-only option. When enabled, GitHub issue imports from the dashboard, CLI, and extension tools persist `githubTracking: { enabled: true }` so Fusion adopts the imported source issue as the tracking issue without turning tracking on for ordinary new tasks. Duplicate/skipped imports do not create tasks or tracking metadata. |
|
||||
@@ -761,8 +762,12 @@ Recovery entrypoints in the dashboard:
|
||||
|
||||
Fusion automatically refreshes Claude/Anthropic OAuth credentials before reporting auth status when the stored OAuth credential includes a refresh token and the access token is expired or within the refresh buffer. For Anthropic, this status path is the `anthropic-subscription` surface (including legacy `anthropic` OAuth rows), not Claude CLI state. A successful refresh updates auth storage and prevents `oauth-token-expired` notifications or startup warnings for that provider, so users usually do not need manual re-login after the initial Claude OAuth login.
|
||||
|
||||
<!-- FNXC:ClaudeOAuth 2026-07-05-00:00: FN-7574 — the proactive-refresh buffer was widened from 60s to 5 minutes (`OAUTH_REFRESH_BUFFER_MS` in packages/engine/src/auth-storage.ts) so a credential nearing expiry is treated as due-for-refresh earlier, without needlessly refreshing well-valid tokens. An engine-side background `OAuthRefreshScheduler` also polls every 5 minutes and reuses this same refresh-if-due logic against every known OAuth provider (plus the anthropic-subscription alias), so healthy subscriptions renew ahead of expiry even if nothing else happens to request a runtime API key in that window. In-flight refresh dedupe and the 30s post-failure cooldown (`OAUTH_REFRESH_FAILURE_COOLDOWN_MS`) still apply. -->
|
||||
|
||||
Manual re-login is still required when no refresh token is stored, the refresh request fails, or the expired OAuth credential belongs to a non-Anthropic provider. In those cases the credential remains expired, `oauth-token-expired` notifications/startup warnings may fire subject to their 12-hour provider throttle, and users should re-authenticate from **Settings → Authentication** or Model Onboarding. The top-level dashboard re-login banner suppresses only the urgent **Anthropic Subscription** entry when **Anthropic API Key** or **Anthropic — via Claude CLI** is already authenticated, so the banner does not imply all Anthropic agent execution is blocked; Settings still shows the subscription OAuth state as expired/not connected until it is refreshed or re-logged-in.
|
||||
|
||||
<!-- FNXC:ProviderAuth 2026-07-05-00:00: FN-7574 — `/api/auth/status` and the engine's OAuthExpiryMonitor previously diverged on what counted as "expired": an oauth-typed credential with a past or missing/non-numeric `expires` could still read authenticated:true from the status route, even after the monitor's oauth-token-expired notification fired. The status route now fails safe: any OAuth credential lacking a usable numeric `expires`, or whose numeric `expires` is in the past and cannot be refreshed, reports expired:true/authenticated:false for both the legacy anthropic-row and separated anthropic-subscription-row storage permutations. -->
|
||||
|
||||
### Anthropic API-key authentication
|
||||
|
||||
Anthropic has three independent authentication/routing paths:
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
---
|
||||
title: "xterm OptionsService no-op reassignment silently skips post-load remeasure"
|
||||
date: 2026-07-04
|
||||
category: ui-bugs
|
||||
module: packages/dashboard/app/components/TerminalModal
|
||||
problem_type: ui_bug
|
||||
component: frontend_terminal
|
||||
applies_when: "Code reapplies an xterm.js Terminal option (fontFamily, fontSize, etc.) to a value that may already equal the terminal's current option value, expecting that reassignment to force an internal recompute (character measurement, renderer dimensions, letter-spacing compensation)."
|
||||
symptoms:
|
||||
- "Mobile terminal text still renders with excessive inter-character spacing on the very first layout even after text-size-adjust is disabled and a document.fonts settle/remeasure step was already added"
|
||||
- "The spacing only 'repairs itself' after an unrelated event: toggling the virtual keyboard, rotating the device, reconnecting the session, or manually changing the font size and changing it back"
|
||||
- "Existing --keyboard-overlap/--vv-height/--vv-width/text-size-adjust: none assertions and a mocked resize(80, 24) all pass while the real-device symptom persists"
|
||||
root_cause: xterm_optionsservice_setter_is_a_strict_noop_on_identical_values_so_reassigning_the_same_resolved_font_after_an_async_settle_never_fires_onoptionchange_and_never_forces_charsizeservice_domrenderer_remeasure
|
||||
resolution_type: code_fix
|
||||
severity: high
|
||||
related_components:
|
||||
- packages/dashboard/app/components/TerminalModal.tsx
|
||||
- packages/dashboard/app/components/SessionTerminal.tsx
|
||||
- packages/dashboard/app/utils/terminalPreferences.ts
|
||||
- packages/dashboard/app/components/__tests__/TerminalModal.test.tsx
|
||||
- packages/dashboard/app/components/__tests__/SessionTerminal.test.tsx
|
||||
- packages/dashboard/app/utils/__tests__/terminalPreferences.test.ts
|
||||
- FN-7456
|
||||
- FN-7460
|
||||
- FN-7561
|
||||
- FN-7567
|
||||
tags:
|
||||
- xterm
|
||||
- font-loading
|
||||
- options-service
|
||||
- mobile-safari
|
||||
- remeasure
|
||||
- letter-spacing
|
||||
- domrenderer
|
||||
---
|
||||
|
||||
# xterm OptionsService no-op reassignment silently skips post-load remeasure
|
||||
|
||||
## Problem
|
||||
|
||||
FN-7561 is the third recurrence of "mobile terminal renders with excessive inter-character spacing" after this exact subsystem was touched twice before:
|
||||
|
||||
- FN-7456 added the iOS keyboard/viewport baseline and a symbols-free measured font stack (see `xterm-symbols-nerd-font-unicode-range.md`).
|
||||
- FN-7460 added `-webkit-text-size-adjust: none` / `text-size-adjust: none` on `.terminal-xterm, .terminal-xterm *` after a real iPhone Safari report showed spacing surviving FN-7456, plus 10px/12px coverage.
|
||||
|
||||
Despite both fixes, the real-device symptom persisted. Both prior fixes treated the browser's DOM text-size-adjust/font-boosting behavior as the entire mechanism. It was not.
|
||||
|
||||
### The actual mechanism
|
||||
|
||||
xterm.js measures character/cell metrics via `CharSizeService`, then `DomRenderer._setDefaultSpacing()` bakes a compensating `letter-spacing` onto `.xterm-rows`:
|
||||
|
||||
```ts
|
||||
// @xterm/xterm src/browser/renderer/dom/DomRenderer.ts
|
||||
private _setDefaultSpacing(): void {
|
||||
// measure same char as in CharSizeService to get the base deviation
|
||||
const spacing = this.dimensions.css.cell.width - this._widthCache.get('W', false, false);
|
||||
this._rowContainer.style.letterSpacing = `${spacing}px`;
|
||||
this._rowFactory.defaultSpacing = spacing;
|
||||
}
|
||||
```
|
||||
|
||||
This recompute only runs from `_handleOptionsChanged()` (wired to `optionsService.onOptionChange`) or from `handleCharSizeChanged()`. Both app terminal surfaces (`TerminalModal.tsx`, `SessionTerminal.tsx`) reapply xterm font options after `waitForTerminalFontMetrics()` (added by FN-7456) settles, expecting that reassignment to force this recompute against the font that only just finished loading. But real xterm's `OptionsService` setter is a strict no-op on an unchanged value:
|
||||
|
||||
```ts
|
||||
// @xterm/xterm src/common/services/OptionsService.ts
|
||||
const setter = (propName: string, value: any): void => {
|
||||
value = this._sanitizeAndValidateOption(propName, value);
|
||||
// Don't fire an option change event if they didn't change
|
||||
if (this.rawOptions[propName] !== value) {
|
||||
this.rawOptions[propName] = value;
|
||||
this._onOptionChange.fire(propName);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
In the common case (the user never touched terminal preferences), the resolved `fontFamily`/`fontSize` after settle are *identical* to what was already applied a few lines earlier at xterm construction/effect setup. Reassigning the same value is therefore a total no-op: no `onOptionChange` fires, `CharSizeService` never remeasures, and `DomRenderer._setDefaultSpacing()` never recomputes the letter-spacing compensation against the now-loaded web font. The stale pre-load cell metrics (measured against a fallback system font before the custom font finished loading) persist as visible excess gaps on the very first mobile layout — exactly matching the report that the terminal "only repairs itself after keyboard toggle/orientation/reconnect": those events happen to force a genuine value change elsewhere in the pipeline (e.g. `handleResize`/`handleDevicePixelRatioChange`), incidentally triggering the missing remeasure.
|
||||
|
||||
Both `TerminalModal.tsx` and `SessionTerminal.tsx` had this bug in **two** places each: the initial xterm-init settle path and the live-preferences-apply settle path.
|
||||
|
||||
## Why FN-7456/FN-7460 missed this
|
||||
|
||||
Both fixes (and their regression tests) only ever asserted the *final* font/size value and CSS text-size-adjust state, never whether a genuine value *transition* occurred inside xterm's internal option pipeline. A plain mock `options: { fontSize: 14 }` object cannot model xterm's no-op-on-unchanged-value contract, so no test could distinguish "the code reassigned the resolved value" (looks correct) from "xterm's internal measurement pipeline actually recomputed" (the real requirement).
|
||||
|
||||
## Solution
|
||||
|
||||
Force a genuine (distinct-value) transition through xterm's option setter every time font metrics settle, regardless of whether the resolved value already equals the terminal's current option value:
|
||||
|
||||
```ts
|
||||
// packages/dashboard/app/utils/terminalPreferences.ts
|
||||
const TERMINAL_FONT_REMEASURE_SENTINEL_FONT_FAMILY = "monospace";
|
||||
|
||||
export function forceTerminalFontRemeasure(
|
||||
terminal: { options: { fontFamily?: string } },
|
||||
fontFamily: string,
|
||||
): void {
|
||||
const sentinel =
|
||||
fontFamily === TERMINAL_FONT_REMEASURE_SENTINEL_FONT_FAMILY
|
||||
? `${TERMINAL_FONT_REMEASURE_SENTINEL_FONT_FAMILY}, monospace`
|
||||
: TERMINAL_FONT_REMEASURE_SENTINEL_FONT_FAMILY;
|
||||
terminal.options.fontFamily = sentinel;
|
||||
terminal.options.fontFamily = fontFamily;
|
||||
}
|
||||
```
|
||||
|
||||
Both assignments run synchronously with no yield in between, so no intermediate frame paints — the terminal never visibly flashes the sentinel font. Both `TerminalModal.tsx` and `SessionTerminal.tsx` now call `forceTerminalFontRemeasure(terminal, resolvedFontFamily)` (instead of a plain `terminal.options.fontFamily = resolvedFontFamily`) at every post-settle site, immediately before reapplying `fontSize` and refitting/resizing/refreshing.
|
||||
|
||||
Do not:
|
||||
|
||||
- Add a hardcoded `letterSpacing`, fixed cell width, or fixed column count to mask the symptom.
|
||||
- Skip the reassignment when the resolved value already matches the current option value — that equality is exactly what causes the bug.
|
||||
- Remove or weaken the FN-7456/FN-7460 `text-size-adjust`/font-stack/keyboard-overlap coverage; this fix is additive to those invariants, not a replacement.
|
||||
|
||||
## Regression coverage
|
||||
|
||||
jsdom cannot exercise real xterm.js internals, so the regression coverage models xterm's documented no-op-on-unchanged-value contract directly on the test double, and asserts the *transition*, not just the final value:
|
||||
|
||||
- Wrap the mocked `Terminal.options` object in a real getter/setter pair with the same equality check as `@xterm/xterm`'s `OptionsService` setter, and track a counter that only increments on a genuine (distinct-value) `fontFamily`/`fontSize` transition.
|
||||
- Simulate the real recurrence: xterm opens before `document.fonts.load()`/`document.fonts.ready` resolve (deferred promises), the resolved font/size are already applied and unchanged once they settle.
|
||||
- Assert the transition counter goes from 0 to a positive count once `waitForTerminalFontMetrics()` settles — this fails pre-fix (a plain reassignment to the same value is a no-op) and passes post-fix (`forceTerminalFontRemeasure` always forces at least one genuine transition).
|
||||
- Add a focused unit test for `forceTerminalFontRemeasure()` itself in `terminalPreferences.test.ts`, covering both "resolved value unchanged" and "resolved value genuinely different" cases.
|
||||
- Cover both `TerminalModal` (mobile viewport, keyboard-open and keyboard-closed initial render) and `SessionTerminal` (embedded attach surface) — both surfaces independently reapply font options after settle and both had the bug.
|
||||
- Run: `pnpm --filter @fusion/dashboard exec vitest run app/components/__tests__/TerminalModal.test.tsx app/components/__tests__/SessionTerminal.test.tsx app/components/__tests__/SessionTerminal.mobile.test.tsx app/__tests__/terminal-input.test.ts app/utils/__tests__/terminalPreferences.test.ts --silent=passed-only --reporter=dot`.
|
||||
- Real mobile Safari/Chrome verification remains the strongest signal for this class of bug; if unavailable, record that as an explicit gap rather than treating desktop WebKit/jsdom as proof (see `docs/ios-acceptance.md`).
|
||||
|
||||
## Recurrence #4 (FN-7567): forcing a genuine remeasure BEFORE `fit()` bakes stale spacing
|
||||
|
||||
FN-7561's `forceTerminalFontRemeasure()` fix (above) is necessary but was not sufficient. On the real
|
||||
mobile device, ordinary ASCII (`test`, `ls`, filenames) still rendered with visible gaps on the
|
||||
initial paint even with the FN-7561 fix, FN-7460's `text-size-adjust: none`, and FN-7456's
|
||||
symbols-free font stack all present.
|
||||
|
||||
### New root cause
|
||||
|
||||
Real xterm's `DomRenderer._setDefaultSpacing()` — the letter-spacing compensation baked onto
|
||||
`.xterm-rows` as `spacing = dimensions.css.cell.width - widthCache.get('W')` — only recomputes from
|
||||
two call sites:
|
||||
|
||||
- `handleCharSizeChanged()`, wired to `CharSizeService.onCharSizeChange`, which fires on any
|
||||
**genuine** (distinct-value) `fontFamily`/`fontSize` option transition — exactly what
|
||||
`forceTerminalFontRemeasure()` forces.
|
||||
- `handleDevicePixelRatioChange()`.
|
||||
|
||||
It is **never** recomputed from `handleResize()` — the path `fitAddon.fit()` →
|
||||
`terminal.resize(cols, rows)` takes.
|
||||
|
||||
Both mobile settle sites in `TerminalModal.tsx` and `SessionTerminal.tsx` (the initial post-font-load
|
||||
settle and the live-preferences-apply settle) called `forceTerminalFontRemeasure()` **before**
|
||||
`fitAddon.fit()`. That correctly forces a genuine option transition and does bake letter-spacing —
|
||||
but it bakes it against the column count that predates the fit. `fitAddon.fit()` then changes the
|
||||
column count (and therefore the true cell width) but never re-bakes the spacing, so the terminal keeps
|
||||
rendering with a spacing value computed against a column count that no longer matches reality. The
|
||||
gap persists until an unrelated later event (device-pixel-ratio change, orientation, reconnect)
|
||||
happens to force another genuine option/DPR-triggered remeasure — exactly matching the report that
|
||||
the terminal "only repairs itself after an incidental refit."
|
||||
|
||||
### Why FN-7456/FN-7460/FN-7561 missed this
|
||||
|
||||
All three prior fixes and their regressions asserted CSS-property presence (`text-size-adjust: none`,
|
||||
symbols-free `fontFamily`) or a remeasure **call count** (`fontRemeasureCount`), never the actual baked
|
||||
letter-spacing value relative to the **post-fit** column count. A test that only checks "a remeasure
|
||||
happened" cannot distinguish "remeasure happened but was baked against stale pre-fit geometry" from
|
||||
"remeasure happened and reflects final geometry."
|
||||
|
||||
### Solution
|
||||
|
||||
Force a **second** genuine remeasure **after** `fitAddon.fit()` settles the column count, so the
|
||||
letter-spacing bake is recomputed against the FINAL (post-fit) geometry, not the pre-fit one:
|
||||
|
||||
```ts
|
||||
// packages/dashboard/app/components/TerminalModal.tsx (mirrored in SessionTerminal.tsx)
|
||||
forceTerminalFontRemeasure(terminal, resolvedFontFamilyRef.current);
|
||||
terminal.options.fontSize = fontSizeRef.current;
|
||||
fitAddon.fit();
|
||||
resizeRef.current?.(terminal.cols, terminal.rows);
|
||||
forceTerminalFontRemeasure(terminal, resolvedFontFamilyRef.current); // re-bake against final cols
|
||||
terminal.refresh(0, Math.max(0, terminal.rows - 1));
|
||||
```
|
||||
|
||||
The `scheduleRefit(rebakeSpacingAfterFit)` path in `TerminalModal.tsx` only re-bakes on the *settled*
|
||||
(font-metrics-ready) call site, not on the immediate first frame — at that point the web font may not
|
||||
have loaded yet, so re-baking there would only bake against fallback-font metrics again.
|
||||
|
||||
Do not:
|
||||
|
||||
- Re-bake unconditionally on every frame/resize — only after a settle that already forced a genuine
|
||||
remeasure and then fit.
|
||||
- Replace this with a hardcoded letter-spacing/cell-width compensation.
|
||||
|
||||
### Regression coverage (geometry-based, not CSS/call-count)
|
||||
|
||||
jsdom cannot exercise real xterm.js internals, so the FN-7567 regression models the real
|
||||
`CharSizeService`/`DomRenderer` contracts directly on the test double instead of a plain mock:
|
||||
|
||||
- `mockHandleCharSizeChanged()` mirrors `CharSizeService.measure()` → `DomRenderer.handleCharSizeChanged()`
|
||||
→ `_setDefaultSpacing()`: recomputes `bakedLetterSpacingPx = cellWidthPx - measuredCharWidthPx` using
|
||||
the **current** (possibly stale, pre-fit) column count, firing only on a genuine option transition.
|
||||
- `mockFitAddonFit()` mirrors `FitAddon.fit()` → `terminal.resize(cols, rows)` →
|
||||
`DomRenderer.handleResize()`: recomputes `cols`/cell-width from the current measured char width but
|
||||
deliberately does **not** touch the baked letter-spacing (matching real xterm).
|
||||
- The assertion is the actual rendered geometry invariant: baked letter-spacing must equal `0` (cell
|
||||
width matches the settled glyph advance width) after the full settle+fit sequence — not merely that
|
||||
`forceTerminalFontRemeasure`/`fontRemeasureCount` was called.
|
||||
- See `TerminalModal.test.tsx` describe block "FN-7567 mobile inter-character spacing (stale post-fit
|
||||
letter-spacing bake)" and the mirrored `SessionTerminal.test.tsx` coverage.
|
||||
- Run: `pnpm --filter @fusion/dashboard exec vitest run app/components/__tests__/TerminalModal.test.tsx app/components/__tests__/SessionTerminal.test.tsx app/components/__tests__/SessionTerminal.mobile.test.tsx app/utils/__tests__/terminalPreferences.test.ts app/__tests__/terminal-input.test.ts --silent=passed-only --reporter=dot`.
|
||||
- Real mobile Safari/Chrome sanity check remains the strongest signal; a real-device screenshot was not
|
||||
obtainable in this execution environment (headless coding agent, no physical device access) — this
|
||||
gap is recorded explicitly rather than treating jsdom/desktop WebKit as proof. See task document
|
||||
key="repro" on FN-7567 and `docs/ios-acceptance.md`.
|
||||
|
||||
## Recurrence #5 (FN-7603): mock/real divergence — Canvas vs DOM character measurement
|
||||
|
||||
FN-7561's `forceTerminalFontRemeasure()` and FN-7567's post-fit re-bake both ran correctly, and both
|
||||
were validated ONLY against jsdom test doubles that never exercise real `@xterm/xterm@5.5.0`. On the
|
||||
reported real mobile device, ordinary ASCII still rendered with visible gaps on the initial paint. This
|
||||
is the fifth recurrence of the same defect, so the FN-7603 executor was required to read the installed
|
||||
`@xterm/xterm@5.5.0`/`@xterm/addon-fit@0.10.0` source before touching production code (see task
|
||||
document key="xterm-source-audit" on FN-7603).
|
||||
|
||||
### The actual mechanism
|
||||
|
||||
xterm's `CharSizeService` selects ONE of two measurement strategies **the moment `terminal.open()`
|
||||
runs**:
|
||||
|
||||
```js
|
||||
// @xterm/xterm/lib/xterm.js (installed 5.5.0), CharSizeService constructor
|
||||
try { this._measureStrategy = new OffscreenCanvasStrategy(optionsService) } // canvas: ctx.measureText("W")
|
||||
catch { this._measureStrategy = new DomFallbackStrategy(document, container, optionsService) } // DOM: offsetWidth/32
|
||||
```
|
||||
|
||||
The Canvas strategy is chosen whenever `OffscreenCanvas` + `CanvasRenderingContext2D.measureText()`
|
||||
reporting `fontBoundingBoxAscent`/`fontBoundingBoxDescent` are available — true on essentially every
|
||||
real modern mobile Safari/Chrome. `dimensions.css.cell.width` (which feeds both `FitAddon.fit()`'s
|
||||
column count, per the installed `addon-fit@0.10.0` `proposeDimensions()`, and
|
||||
`DomRenderer._setDefaultSpacing()`'s baked letter-spacing) derives from whichever strategy
|
||||
`CharSizeService` picked.
|
||||
|
||||
Separately, `DomRenderer._setDefaultSpacing()` and `DomRendererRowFactory.createRow()`'s per-glyph
|
||||
override BOTH measure via `WidthCache`, which is **always** DOM-based (`offsetWidth` of a hidden
|
||||
32×-repeated-character span) — entirely independent of `CharSizeService`'s strategy choice. Real glyphs
|
||||
are painted 100% through the DOM (`DomRenderer` never draws through canvas). Canvas 2D text measurement
|
||||
and DOM/CSS text layout are two different browser rendering pipelines that can disagree — even by a
|
||||
fraction of a device pixel — for the same font on the same device; this is a documented,
|
||||
long-standing cross-API text-metrics inconsistency. `_setDefaultSpacing()`'s formula
|
||||
(`dimensions.css.cell.width - widthCache.get('W')`) only correctly converges to zero (tight, contiguous
|
||||
cells) when both operands are measured through the SAME pipeline. None of FN-7456/FN-7460/FN-7561/
|
||||
FN-7567 (or their test doubles) ever modeled this — all four assumed CharSizeService's measurement and
|
||||
WidthCache's measurement were the same value.
|
||||
|
||||
### Why FN-7456/FN-7460/FN-7561/FN-7567 missed this
|
||||
|
||||
Every prior fix's test double (`mockHandleCharSizeChanged`) treated the measured character width as a
|
||||
single shared value used for both "the cell width that drives fit" and "the width WidthCache subtracts
|
||||
in `_setDefaultSpacing()`" — a faithful-looking model of xterm's DOM-only fallback strategy, but NOT of
|
||||
the Canvas strategy that real xterm actually selects by default on real mobile browsers. Because jsdom
|
||||
cannot run real `@xterm/xterm`, and no fix before FN-7603 cross-checked the double against the installed
|
||||
source, the divergence between "what CharSizeService measures" (Canvas, in the real common case) and
|
||||
"what WidthCache measures" (always DOM) went completely uncovered for four recurrences.
|
||||
|
||||
### Solution
|
||||
|
||||
Force `CharSizeService` to construct with its own DOM fallback strategy — unifying the cell-width
|
||||
measurement with `WidthCache`'s measurement — by making `OffscreenCanvas` transiently unavailable for
|
||||
the synchronous duration of `terminal.open()` (where `CharSizeService` is constructed):
|
||||
|
||||
```ts
|
||||
// packages/dashboard/app/utils/terminalPreferences.ts
|
||||
export function withDomBasedTerminalCharacterMeasurement<T>(fn: () => T): T {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(window, "OffscreenCanvas");
|
||||
delete (window as any).OffscreenCanvas;
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
if (descriptor) Object.defineProperty(window, "OffscreenCanvas", descriptor);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Both `TerminalModal.tsx` and `SessionTerminal.tsx` now wrap their `terminal.open(container)` call in
|
||||
`withDomBasedTerminalCharacterMeasurement(() => terminal.open(container))`. `CharSizeService`'s
|
||||
constructor try-block throws (no `OffscreenCanvas` global), so it self-selects the SAME DOM-based
|
||||
strategy `WidthCache` already always uses — no hardcoded letter-spacing/cell-width compensation is
|
||||
added; the fix unifies the measurement pipeline instead.
|
||||
|
||||
Do not:
|
||||
|
||||
- Patch `window.OffscreenCanvas` outside the narrow synchronous `open()` window — other page code
|
||||
(charts, canvas-based rendering elsewhere in the dashboard) may legitimately need it.
|
||||
- Assume this is scoped to mobile only — desktop with the DOM renderer (WebGL addon failed to load, or
|
||||
`renderer: "canvas"` preference) has the identical divergence and benefits from the same fix.
|
||||
- Treat this as a replacement for FN-7561/FN-7567 — both remain necessary; this fix addresses a
|
||||
different, independent measurement-pipeline mismatch.
|
||||
|
||||
### Regression coverage (Canvas-vs-DOM divergence, not CSS/call-count)
|
||||
|
||||
- Extended the FN-7567 double: `mockCanvasCharWidthPx` (drives `FitAddon.fit()`'s column count,
|
||||
mirroring `dimensions.css.cell.width`) can diverge from `mockDomCharWidthPx` (mirrors
|
||||
`WidthCache.get('W')`) by a fixed offset, gated on `window.OffscreenCanvas` being defined at the
|
||||
moment the mock's `open()` runs — exactly mirroring the real `CharSizeService` constructor's
|
||||
try/catch strategy selection.
|
||||
- The assertion is the same rendered-geometry invariant as FN-7567 (baked letter-spacing `== 0`), but
|
||||
now fails on HEAD even with the full FN-7561/FN-7567 settle+pre/post-fit-remeasure sequence present,
|
||||
because the divergence is NOT an ordering bug — it's a measurement-pipeline bug those fixes cannot
|
||||
see or fix.
|
||||
- See `TerminalModal.test.tsx` describe block "FN-7603 mobile inter-character spacing (Canvas vs DOM
|
||||
CharSizeService measurement divergence)".
|
||||
- Run: `pnpm --filter @fusion/dashboard exec vitest run app/components/__tests__/TerminalModal.test.tsx app/components/__tests__/SessionTerminal.test.tsx app/components/__tests__/SessionTerminal.mobile.test.tsx app/utils/__tests__/terminalPreferences.test.ts app/__tests__/terminal-input.test.ts --silent=passed-only --reporter=dot`.
|
||||
- Real mobile Safari/Chrome sanity check remains the strongest signal; a real-device screenshot was not
|
||||
obtainable in this execution environment — this gap is recorded explicitly (task document
|
||||
key="repro" on FN-7603) rather than treating jsdom/desktop WebKit as proof.
|
||||
@@ -162,6 +162,16 @@ These appear in task activity history; run-audit entries are emitted where run c
|
||||
|
||||
Recovery is reversible: restore archived tasks via dashboard **Unarchive** or `fn_task_unarchive`.
|
||||
|
||||
#### Revert/Undo affordance (FN-7525)
|
||||
|
||||
Done and Archived task cards (board card inline row + context menu, the detail view, and the list context menu) expose a **Revert** action alongside Archive/Unarchive when the task has a landed commit to revert. Clicking it calls `POST /tasks/:id/revert` in `"auto"` mode:
|
||||
|
||||
- A clean git revert shows a success toast naming the created revert commit sha.
|
||||
- A conflicting/unsupported git result opens a confirm dialog offering to create an AI-undo task; confirming re-calls the route in `"ai"` mode and surfaces the created task id (or that an undo task is already open).
|
||||
- A `needsHuman` result (e.g. auto-merge is off) is surfaced as an informational/error toast, never silently forked into an AI task.
|
||||
|
||||
The source task's column/lifecycle is never mutated as a side effect of a revert; the Revert affordance is absent when the task has no landed commit or when the hosting surface does not support it.
|
||||
|
||||
### 2) Plan Mode (AI interview)
|
||||
|
||||
On desktop/tablet, open **Planning** from the left sidebar to start or resume a planning session. You can also hand a draft from the board quick-entry row or New Task dialog to Planning with the **Plan** action.
|
||||
@@ -542,8 +552,8 @@ The task detail modal exposes multiple tabs:
|
||||
|
||||
After planning, each task gets a structured `PROMPT.md` with sections like:
|
||||
|
||||
- Mission
|
||||
- Before → after transformation summary
|
||||
- Mission
|
||||
- Dependencies
|
||||
- Context to read first
|
||||
- File scope
|
||||
@@ -673,13 +683,23 @@ Recovery/backfill guidance:
|
||||
- If both the active row and archive snapshot were overwritten, Fusion cannot reconstruct lost attachments/comments automatically; recreate them from git history, branch/worktree contents, screenshots, or external issue trackers.
|
||||
- Record the incident in the replacement task so future audits understand why the task ID and commit history diverge.
|
||||
|
||||
## Reverting Done/Archived tasks (git path)
|
||||
## Reverting Done/Archived tasks (git path + AI-undo fallback)
|
||||
|
||||
- `POST /api/tasks/:id/revert` (FN-7523) reverts a **Done** or **Archived** task's landed work via git. Only `done`/`archived` tasks are revertable; the source task's column/status is never mutated as a side effect.
|
||||
- The engine resolves the task's attributable commit(s) (squash single-commit, rebase/cherry-pick trailer-filtered subset, or lineage-snapshot fallback), performs a non-committing dry-run to classify the outcome, and only writes a real commit when the dry-run is clean.
|
||||
- Response contract: `{ mode: "git", clean, revertCommitSha?, conflicts?, alreadyReverted?, unsupported?, needsHuman?, reason? }`. A clean revert lands a `revert(FN-xxxx): ...` commit carrying a `Fusion-Task-Id` trailer on the resolved base branch. A conflicting result creates no commit and leaves the tree/HEAD untouched — this is where a future AI-undo fallback (sibling task) can take over.
|
||||
- Workspace (multi-repo) tasks and `autoMerge:false` projects are out of scope for the forced git write and return `unsupported`/`needsHuman` results instead.
|
||||
- This is the git path only; no dashboard UI affordance ships with it (see sibling follow-up tasks for the card action and the AI-undo fallback).
|
||||
- The route accepts an optional request body `{ mode?: "git" | "ai" | "auto" }` (default `"auto"`; unknown values reject with 400):
|
||||
- `"git"` — the FN-7523 git-only behavior. The result (including a conflicting/unsupported result) is returned as-is and never creates a follow-up task.
|
||||
- `"ai"` — skip git entirely and always create the AI-undo fallback task (FN-7524).
|
||||
- `"auto"` — try git first. A clean/alreadyReverted/needsHuman result is returned unchanged. A conflicting or unsupported result falls back to creating the AI-undo task.
|
||||
- Also accepts an optional `{ granularity?: "squash" | "per-sha" }` field (FN-7548) that selects the git-path commit granularity: `"squash"` (default, unchanged) accumulates all attributable commits into one revert commit; `"per-sha"` creates one attributed revert commit per original sha (each with its own `Fusion-Task-Id` trailer and audit line), skipping no-op shas without empty commits. A mid-batch conflict in either mode rolls back the whole batch — no partially-landed per-sha commits. This field only affects the single-repo git path and is ignored when `mode` resolves to `"ai"` or the task is a workspace task.
|
||||
- Git-path response contract (additive only): `{ mode: "git", clean, revertCommitSha?, revertCommitShas?, conflicts?, alreadyReverted?, unsupported?, needsHuman?, reason? }`. A clean revert lands a `revert(FN-xxxx): ...` commit carrying a `Fusion-Task-Id` trailer on the resolved base branch; `revertCommitShas` reports every commit created (all of them for `per-sha`, the single one for `squash`) alongside the existing `revertCommitSha`.
|
||||
- AI-undo response contract: `{ mode: "ai", createdTaskId: "FN-YYYY", alreadyOpen?: true }`. The created task is an ordinary `triage`-column board task (via the normal `store.createTask` path) that references the source task's id, mission, and landed files, and instructs undoing the source task's behavior while preserving unrelated later changes to the same files, using a `revert(FN-xxxx): ...` commit convention. It carries NO dependency on the (already done/archived) source task. A `sourceMetadata.revertOf` marker makes repeated fallback calls idempotent — while an AI-undo task for that source is still open, a further call returns the same `createdTaskId` with `alreadyOpen: true` instead of creating a duplicate; a prior undo task that itself reached `done`/`archived` does not suppress a fresh one.
|
||||
- **Workspace (multi-repo) tasks (FN-7547):** tasks with `workspaceWorktrees` populated (`isWorkspaceTask`) are revertable too — the route dispatches to a dedicated workspace path that reasons about every sub-repo's integration branch as ONE all-or-nothing unit. It resolves each sub-repo's attributable commit(s), dry-run classifies every sub-repo first, and only commits a `revert(FN-xxxx): ...` commit on EACH sub-repo when every sub-repo classifies clean/already-reverted; if any sub-repo conflicts, no sub-repo is committed and every touched sub-repo worktree is rolled back to its pre-call state. Response contract for workspace tasks: `{ mode: "git", clean, workspace: { repos: [{ repo, classification, revertCommitSha?, conflicts?, alreadyReverted? }] }, conflicts?: {repo, file, ...}[] }`. A conflicting workspace result still falls back to the AI-undo task under `"auto"` mode, same as a single-repo conflicting result.
|
||||
- **`autoMerge:false` PR-based revert (FN-7554):** for a single-repo task whose git revert classifies **clean**, `autoMerge:false` no longer dead-ends at `needsHuman`. The route prepares a dedicated `fusion/revert-<id>` branch off the resolved base branch (via the engine's `prepareRevertPrBranch`, which NEVER writes to the base branch itself), pushes it, and opens a GitHub PR through the same owner/repo resolution, `githubRateLimiter` gate, `findPrForBranch` idempotency, and `manual: true` handoff as `POST /tasks/:id/pr/create`. Response: `{ mode: "pr", clean: true, prUrl, prNumber, revertBranch, existingPr? }` — a second call while the PR is still open links the existing PR (`existingPr: true`) instead of re-pushing. GitHub unconfigured or rate-limited still degrades gracefully to `{ mode: "git", needsHuman: true, reason }`, and a conflicting/unsupported/already-reverted classification is unaffected (no PR is opened; `"auto"` mode still falls back to the AI-undo task on conflict/unsupported).
|
||||
- **`autoMerge:false` PR-based revert extended to workspace tasks (FN-7577):** a workspace task whose git revert classifies **clean across every sub-repo** also opens PRs instead of dead-ending at `needsHuman` under `autoMerge:false`. The engine's `prepareWorkspaceRevertPrBranches` mirrors the workspace all-or-nothing classify-all contract: it dry-run classifies EVERY sub-repo first, and only prepares one `fusion/revert-<id>` branch per sub-repo (never writing any sub-repo's integration branch) when every sub-repo classifies clean/already-reverted — a single conflicting sub-repo aborts the WHOLE preparation with no branch created anywhere. The route then resolves owner/repo and checks the rate limiter for EVERY sub-repo before pushing/creating any PR (so a GitHub-unconfigured or rate-limited sub-repo degrades the whole task to `needsHuman` rather than opening a partial subset), then opens one PR per sub-repo reusing FN-7554's per-sub-repo `findPrForBranch` idempotency and `manual: true` handoff. Response: `{ mode: "pr", clean: true, workspace: { repos: [{ repo, revertBranch, prUrl, prNumber, existingPr? }] } }`. Existing `{ mode: "git" | "ai" | "pr" }` shapes, the `autoMerge:true` workspace path, and FN-7554's single-repo path are unchanged.
|
||||
- **Dashboard auto-linking (FN-7555):** the AI-undo task's card shows an "Undo of FN-xxxx" chip and its detail view shows a clickable "Created to undo FN-xxxx" link back to the source task. The source task's detail view shows an "Undo task: FN-YYYY" link whenever an OPEN undo task referencing it exists in the loaded tasks (matching `TaskStore.findOpenRevertTaskForSource`'s open-only semantics — a `done`/`archived`/soft-deleted undo task is never surfaced as active). Both directions are derived client-side from `sourceMetadata.revertOf`; no new API. A dedicated Done/Archived card revert-trigger action is still a separate follow-up (see FN-7525).
|
||||
- **Configurable AI-undo workflow default (FN-7556, UI: FN-7578):** the project setting `aiUndoTaskWorkflowId` (default `builtin:review-heavy`) selects the workflow applied to every AI-undo task created above (`mode:"ai"` and the `auto`/workspace conflict fallbacks all share one creation seam, so all three inherit this default) — a stricter review posture is warranted because these tasks reverse already-shipped code. A blank/unset value means the created task inherits the project default workflow (pre-FN-7556 behavior); the route falls back to inherit (with a logged warning) if the configured id is blank or does not resolve to a real workflow, so a misconfigured id never breaks AI-undo task creation. Editable from **Settings → General → AI-undo task workflow** (choose "Inherit project default workflow" to store the blank/inherit sentinel). See [Settings Reference → Project Settings](./settings-reference.md#project-settings).
|
||||
|
||||
|
||||
## GitHub Issue Import and PR Creation
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ Use this inventory as the documentation map for current workflow behavior:
|
||||
| Routing boundary | Agents may select/change a workflow only for explicit user requests or tasks they created; no-commit markers do not imply Quick fix or any other workflow. | This page, [Selecting workflows](#selecting-workflows); [Agents](./agents.md#interactive-cli-chat). |
|
||||
| Dashboard board/list/graph selection | Board/List/Header/Graph share durable per-project workflow selection; stale saved ids fall back to a valid workflow. Board adds a dashboard-only **All workflows** aggregate and task workflow-name badges; Graph uses **All workflows** for the full active graph. | [Dashboard Guide → Board View](./dashboard-guide.md#board-view), [Graph View](./dashboard-guide.md#graph-view), and [Workflow Selection and Editor](./dashboard-guide.md#workflow-selection-and-editor). |
|
||||
| Create/planning forwarding | Quick-create task creation, Planning Mode, Subtask Breakdown, and the New Task dialog forward the active real workflow id when creating tasks; **All workflows** quick-create chooses a real workflow intake/default column instead of saving a synthetic aggregate id. | [Dashboard Guide → Planning Mode](./dashboard-guide.md#planning-mode). |
|
||||
| Manual-intake column parking | Dashboard create surfaces never send an explicit `column`; the store resolves the landing column from the (selected or project-default) workflow's intake column. A workflow whose intake column sets `autoTriage: false` (e.g. built-in Coding (Ideas)'s `ideas` column) parks new cards there instead of auto-planning them, until an operator promotes the card. The full lifecycle — create → parked → operator "Start" promotion → poll-time todo-discovery of the still-unplanned (bootstrap-stub) card — is regression-tested at the engine (triage poll ordering/discovery), UI (`TaskCard` Start affordance), and store (create → `moveTask` promotion) layers (FN-7596). | [Dashboard Guide → Create/Planning Forwarding](./dashboard-guide.md#planning-mode). |
|
||||
|
||||
### Skill-backed workflow steps
|
||||
|
||||
@@ -212,6 +213,10 @@ If the Plan Review reviewer is unavailable before producing a verdict, the task
|
||||
|
||||
Workflow Plan Review is separate from manual plan approval. Project `planApprovalMode: "auto-approve-all"` bypasses only the final manual `awaiting-approval` plan gate after the plan is specified and any enabled Plan Review passes; it does not disable Plan Review, release authorization, or other explicit safety gates.
|
||||
|
||||
**FN-7559 — telling the three holds apart:** Plan Review parks a task with its own distinct statuses (`needs-replan` for a revision verdict, `plan-review-unavailable` for a reviewer-outage retry), so it never renders identically to a plan-approval hold. The release-authorization gate and the manual plan-approval gate, however, both use `status: "awaiting-approval"` — auto-approve-all bypasses the manual gate but never the release-authorization gate, so a release-class task (or a user-authored task missing the explicit authorization marker) still parks even with auto-approve-all on. To make that unambiguous to the operator, the task carries `awaitingApprovalReason: "release-authorization"` only when the release-authorization gate is the one holding it; the dashboard renders a distinct "Awaiting Release Authorization" label and hides the Approve/Reject Plan buttons for that hold instead of showing the generic manual-approval affordance.
|
||||
|
||||
**FN-7569 — manual plan approval is idempotent against unchanged plans:** approving a plan under the manual gate records a fingerprint of the exact approved `PROMPT.md`. If the same task is later re-specified — a `needs-replan` replan, a Plan Review reviewer-outage retry, or a self-healing rebound back to `triage` — and produces byte-identical `PROMPT.md` content, the manual gate detects the match and proceeds straight to `todo` instead of re-parking at `status: "awaiting-approval"`. A genuinely revised plan still produces a different fingerprint and re-asks as before, and using Reject Plan clears the fingerprint so the regenerated plan is always treated as new. This idempotency check runs only inside the manual gate, strictly after release authorization and Plan Review have already made their independent decisions, and never applies under `planApprovalMode: "auto-approve-all"` (which bypasses the manual gate entirely).
|
||||
|
||||
`builtin:legacy-coding` is backed by the original monolithic `BUILTIN_CODING_WORKFLOW_IR`: `planning` → `execute` → optional quality gates → `review` → merge region.
|
||||
|
||||
`builtin:stepwise-coding` displays as Coding (per-step review). It is backed by `BUILTIN_STEPWISE_CODING_WORKFLOW_IR`; it keeps the same lifecycle columns/traits while adding the default-on optional Plan Review before `parse-steps`, modeling per-step parse/execute/review/rework as authored graph structure, and retaining the post-foreach optional Code Review gate before its final review/merge region.
|
||||
@@ -352,6 +357,34 @@ Parallelism is opt-in *per step by the planner*, not asserted by the workflow au
|
||||
|
||||
Notification delivery is intentionally best-effort: a missing/unconfigured notification service, an empty `event`, or a provider delivery failure is logged/audited but does not fail the workflow node. Providers receive the rendered title/message in notification metadata so ntfy and webhook notifications can show workflow-specific copy. `workflow-notify` is **not** part of the default ntfy event allowlist; add it to `ntfyEvents` or the provider `events` filter when you want workflow-authored notifications delivered.
|
||||
|
||||
#### `ask-user` node — chat reach-out (FN-7579)
|
||||
|
||||
`ask-user` (`{ question? }`) reaches out to the user from inside a running task: it parks the task with `status: "awaiting-user-input"`, `paused: true`, and `pausedReason` carrying a `workflow-input:<nodeId>@<pauseEpochMs>: <question>` marker; the question surfaces in the task chat/detail (and via the `planning-awaiting-input` notification). Once the user replies (a steering comment at/after the pause watermark) and unpauses the task, the node resumes, clears its marker, and publishes the answer downstream at context key `input:<nodeId>` (readable by, for example, a downstream `exit-gate`'s condition). `question` falls back to `config.prompt`, then the shared default string ("This workflow is waiting for your input.") when both are omitted.
|
||||
|
||||
`ask-user` is a first-class, discoverable promotion of plumbing that already existed: a `prompt` node with `config.awaitInput: true` pauses/resumes identically (`runAwaitInputNode`). That shape remains a **fully supported back-compat alias** — existing workflows using it are unaffected — `ask-user` is simply the dedicated palette entry and IR node kind for new authoring.
|
||||
|
||||
#### `exit-gate` node — early workflow termination (FN-7579)
|
||||
|
||||
`exit-gate` (`{ condition? }`) lets a workflow route directly to the terminal `end` node instead of always walking the full graph. It is validated to always have a (transitive, non-rework) path to `end` so it can never strand the graph, but it is **not** itself an `end` node — only a router onto one.
|
||||
|
||||
With no `condition`, an exit-gate always exits (`outcome:exit`). With a `condition` (the same shape as a `loop` node's `exitWhen`: `{ type: "output-contains", nodeId?, value }` or `{ type: "output-matches", nodeId?, pattern, flags? }`), the gate reads `context["input:<nodeId>"]` — the same key an `ask-user` node's answer is published under — and exits (`outcome:exit`) when it matches, or falls through (`outcome:continue`) otherwise. Route `outcome:exit` to `end` and `outcome:continue` back into the loop (or onward) as needed. A malformed condition (bad regex, missing referenced value) degrades to "no match" rather than throwing.
|
||||
|
||||
#### Brainstorming / chat reach-out composition
|
||||
|
||||
Compose `ask-user` + `exit-gate` for a brainstorming phase that loops until the user approves, then proceeds:
|
||||
|
||||
```
|
||||
start → ask (ask-user: "Anything to refine?")
|
||||
→ exit (exit-gate: condition { type: "output-contains", nodeId: "ask", value: "looks good" })
|
||||
── outcome:exit ──→ end (or onward into the normal plan/execute path)
|
||||
── outcome:continue ──→ ask (rework edge back to the ask-user node; mark the ask-user
|
||||
node `config.reworkRegion: true` and the edge `kind: "rework"`,
|
||||
mirroring the top-level rework-region convention U6 uses for
|
||||
the PR review loop)
|
||||
```
|
||||
|
||||
Each turn, the user is asked to refine; once they reply "looks good" (or whatever the condition matches), the exit-gate routes the task out of the brainstorm loop. This composition is also available as a discoverable built-in: `builtin:brainstorming` (FN-7584) registers exactly this shape — `ask-user` → a refine prompt step → `exit-gate`-on-approval — ahead of the unmodified default Coding plan/execute/review/merge spine, selectable directly from the workflow picker. Copy the shape into a custom workflow's IR via `fn_workflow_create`/`fn_workflow_update` when you need a different downstream pipeline than the standard coding one.
|
||||
|
||||
#### Workflow-defined custom task fields
|
||||
|
||||
Workflows declare typed task fields via IR `fields: [{ id, name, type, required?, default?, options?, render? }]` (`type ∈ string | text | number | boolean | enum | multi-enum | date | url`; `options` for enum kinds; `render.placement ∈ card | detail | detail-section`, `render.widget`, `render.badge`). Values live in `tasks.customFields` and are validated through a single store authority (`updateTaskCustomFields`) with typed rejections (offending `fieldId` + `code`). Editing or switching a workflow **orphans** (never destroys) values for removed/incompatible fields — orphans are retained and shown under a detail disclosure. The task UI renders the schema dynamically (detail-form widgets by type, up to 3 card badges by placement). Agents read/write fields via `fn_task_update`'s `custom_fields` patch; authors set them via `fn_workflow_create/update`. Field values are surfaced in task/session context.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fusion-workspace",
|
||||
"version": "0.55.0",
|
||||
"version": "0.56.1",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
||||
|
||||
@@ -1,5 +1,126 @@
|
||||
# runfusion.ai
|
||||
|
||||
## 0.56.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ed823c7]
|
||||
- Updated dependencies [dc44730]
|
||||
- Updated dependencies [b9d60b3]
|
||||
- Updated dependencies [e347062]
|
||||
- Updated dependencies [f4f1656]
|
||||
- @runfusion/fusion@0.56.1
|
||||
|
||||
## 0.56.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [8668a05]
|
||||
- Updated dependencies [978cdda]
|
||||
- Updated dependencies [635fca2]
|
||||
- Updated dependencies [3d55102]
|
||||
- Updated dependencies [0f1cd0a]
|
||||
- Updated dependencies [b42ba9f]
|
||||
- Updated dependencies [a7559b0]
|
||||
- Updated dependencies [4b530a6]
|
||||
- Updated dependencies [a5ac3c3]
|
||||
- Updated dependencies [8912399]
|
||||
- Updated dependencies [d16c8b4]
|
||||
- Updated dependencies [b800f7d]
|
||||
- Updated dependencies [315f3bc]
|
||||
- Updated dependencies [9dc248e]
|
||||
- Updated dependencies [52dbc0e]
|
||||
- Updated dependencies [ced783e]
|
||||
- Updated dependencies [50cdab1]
|
||||
- Updated dependencies [50786f2]
|
||||
- Updated dependencies [b4b1f6d]
|
||||
- Updated dependencies [e8b7362]
|
||||
- Updated dependencies [0900a38]
|
||||
- Updated dependencies [a2b09f2]
|
||||
- Updated dependencies [0f05156]
|
||||
- Updated dependencies [20184ac]
|
||||
- Updated dependencies [82493e0]
|
||||
- Updated dependencies [5689346]
|
||||
- Updated dependencies [b42be87]
|
||||
- Updated dependencies [2f23d22]
|
||||
- Updated dependencies [efa8105]
|
||||
- Updated dependencies [61c8bdc]
|
||||
- Updated dependencies [e8dc2ae]
|
||||
- Updated dependencies [d2e3134]
|
||||
- Updated dependencies [b0208c1]
|
||||
- Updated dependencies [7d8a1b8]
|
||||
- Updated dependencies [2797803]
|
||||
- Updated dependencies [a2d6349]
|
||||
- Updated dependencies [4baa4c4]
|
||||
- Updated dependencies [87a700c]
|
||||
- Updated dependencies [68f5153]
|
||||
- Updated dependencies [aa757bc]
|
||||
- Updated dependencies [0689250]
|
||||
- Updated dependencies [12a6d1b]
|
||||
- Updated dependencies [81f2053]
|
||||
- Updated dependencies [2cc84b5]
|
||||
- Updated dependencies [79ab367]
|
||||
- Updated dependencies [c16cc9e]
|
||||
- Updated dependencies [aae603b]
|
||||
- Updated dependencies [d10ea9a]
|
||||
- Updated dependencies [bf68839]
|
||||
- Updated dependencies [53d7b7e]
|
||||
- Updated dependencies [c4d81fe]
|
||||
- Updated dependencies [e7cb2f1]
|
||||
- Updated dependencies [4707eb5]
|
||||
- Updated dependencies [3b52a4d]
|
||||
- Updated dependencies [5ad8ec8]
|
||||
- Updated dependencies [726cbf8]
|
||||
- Updated dependencies [36bd74e]
|
||||
- Updated dependencies [df0be88]
|
||||
- Updated dependencies [ec9ac61]
|
||||
- Updated dependencies [8d36b99]
|
||||
- Updated dependencies [ad744aa]
|
||||
- Updated dependencies [5c3d58a]
|
||||
- Updated dependencies [b4be515]
|
||||
- Updated dependencies [62ddb19]
|
||||
- Updated dependencies [883c73e]
|
||||
- Updated dependencies [2ed06f9]
|
||||
- Updated dependencies [8c6f76c]
|
||||
- Updated dependencies [d09b57f]
|
||||
- Updated dependencies [3d58260]
|
||||
- Updated dependencies [052a277]
|
||||
- Updated dependencies [f992e6a]
|
||||
- Updated dependencies [2df6c35]
|
||||
- Updated dependencies [94e9d15]
|
||||
- Updated dependencies [3dd227b]
|
||||
- Updated dependencies [6e4c207]
|
||||
- Updated dependencies [6d364fc]
|
||||
- Updated dependencies [b471aec]
|
||||
- Updated dependencies [9d4a45b]
|
||||
- Updated dependencies [72b77bf]
|
||||
- Updated dependencies [c08498e]
|
||||
- Updated dependencies [24b27e8]
|
||||
- Updated dependencies [fb45157]
|
||||
- Updated dependencies [7c0be53]
|
||||
- Updated dependencies [71dfd3a]
|
||||
- Updated dependencies [9592e3a]
|
||||
- Updated dependencies [c31f9ef]
|
||||
- Updated dependencies [ce9df29]
|
||||
- Updated dependencies [78d4db9]
|
||||
- Updated dependencies [196abb5]
|
||||
- Updated dependencies [7435849]
|
||||
- Updated dependencies [73b38ba]
|
||||
- Updated dependencies [42bbe58]
|
||||
- Updated dependencies [45e5a26]
|
||||
- Updated dependencies [a1a6b09]
|
||||
- Updated dependencies [53fe0d7]
|
||||
- Updated dependencies [cf3fe8b]
|
||||
- Updated dependencies [8b4e522]
|
||||
- Updated dependencies [f30d55f]
|
||||
- Updated dependencies [20379e8]
|
||||
- Updated dependencies [e0f3d3d]
|
||||
- Updated dependencies [5b193d2]
|
||||
- Updated dependencies [ecbbb29]
|
||||
- Updated dependencies [546ef16]
|
||||
- Updated dependencies [b173f76]
|
||||
- @runfusion/fusion@0.56.0
|
||||
|
||||
## 0.55.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "runfusion.ai",
|
||||
"version": "0.55.0",
|
||||
"version": "0.56.1",
|
||||
"license": "MIT",
|
||||
"description": "Launch Fusion with `npx runfusion.ai` — tiny alias for @runfusion/fusion.",
|
||||
"homepage": "https://runfusion.ai",
|
||||
|
||||
@@ -1,5 +1,345 @@
|
||||
# @runfusion/fusion
|
||||
|
||||
## 0.56.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ed823c7: summary: Fix Anthropic subscription showing "logged in" while all model calls fail.
|
||||
category: fix
|
||||
dev: Two-part fix. (1) OAuth token refresh in `packages/engine/src/auth-storage.ts` sent a `scope` param (defaulting to `user:profile`), which per RFC 6749 §6 re-issued the access token narrowed to that scope and stripped `user:inference` — so refreshed tokens 403'd on every model call. Refresh now omits `scope` (preserving the originally-granted scopes, matching pi-ai's own refresh), and `ANTHROPIC_DEFAULT_SCOPES` mirrors the full Claude Code scope set. (2) `/auth/status` now reports an unexpired Anthropic OAuth token that lacks an inference scope as not-connected (authenticated:false, expired:true so the re-login banner fires) with a scope-specific loginError, instead of falsely claiming a live session. Existing narrowed tokens need one re-login to obtain a fresh broad grant.
|
||||
- dc44730: summary: Fix "Invalid transition" error when moving cards out of a custom workflow column like Coding (Ideas) → Ideas.
|
||||
category: fix
|
||||
dev: moveTaskInternal's compat-flag legacy path validated moves against the legacy VALID_TRANSITIONS table, which is keyed only by the built-in column ids; a task in a non-legacy workflow column (e.g. "ideas") had no key and every move was rejected. The legacy branch now resolves a non-legacy source column's targets from the task's own workflow adjacency (resolveAllowedColumns) while preserving the legacy bare-Error contract for legacy columns.
|
||||
- b9d60b3: summary: Fix overlapping Record and Clear buttons in the Keyboard Shortcuts settings rows on desktop and mobile.
|
||||
category: fix
|
||||
dev: The shortcut-capture Record/Clear buttons no longer use the icon-only `btn-icon` class (which set `line-height:0` and a mobile 36px square, clipping/overlapping the text labels); they use a text-button class and the `.shortcut-capture` row locks buttons with `flex-shrink:0` so the input and controls never overlap, stacking cleanly on mobile.
|
||||
- e347062: summary: Fix persistent mobile terminal inter-character spacing (5th recurrence root cause).
|
||||
category: fix
|
||||
dev: xterm's CharSizeService picks a Canvas-based (OffscreenCanvas) or DOM-based character-measurement strategy at terminal.open() time; DomRenderer's letter-spacing bake always measures via a separate DOM-based WidthCache, so a Canvas-vs-DOM measurement mismatch survived FN-7561/FN-7567's remeasure-ordering fixes. `withDomBasedTerminalCharacterMeasurement` in terminalPreferences.ts forces CharSizeService onto the same DOM strategy for both TerminalModal and SessionTerminal.
|
||||
- f4f1656: summary: Fix manual PR actions hidden when a task auto-merge override was on but global auto-merge was off.
|
||||
category: fix
|
||||
dev: TaskDetailModal isManualPrFlow now keys off live global autoMergeEnabled, not the per-task effective override (regression from FN-7255).
|
||||
|
||||
## 0.56.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d16c8b4: summary: Expand first-run AI provider quick-start choices beyond Anthropic.
|
||||
category: feature
|
||||
dev: Moves advanced/all-provider onboarding controls under the quick-start provider section.
|
||||
- 315f3bc: summary: Show Git prerequisite guidance during first-run GitHub onboarding.
|
||||
category: feature
|
||||
dev: Adds bounded server-host git availability to auth status and onboarding.
|
||||
- 50cdab1: summary: Add GitHub OAuth and CLI setup actions to first-run onboarding.
|
||||
category: feature
|
||||
dev: GitHub onboarding now shows in-flow OAuth connect, gh auth login, and gh install guidance.
|
||||
- 2f23d22: summary: Add configurable dashboard keyboard shortcuts for Quick Chat and Terminal.
|
||||
category: feature
|
||||
dev: Global dashboardKeyboardShortcuts settings, guarded document-level key handling, and Escape topmost-popup dismissal.
|
||||
- efa8105: summary: Add search in Settings so operators can find settings faster.
|
||||
category: feature
|
||||
dev: Dashboard Settings filters visible sections by setting labels and keywords.
|
||||
- 7d8a1b8: summary: Add a pinned below-application layout option for the dashboard terminal.
|
||||
category: feature
|
||||
dev: Terminal display mode now supports persisted docked, floating, and below layouts, with header controls replacing the footer shell.
|
||||
- 87a700c: summary: Add a Reset Settings button to restore a menu's or all project settings to defaults.
|
||||
category: feature
|
||||
dev: New tested section→keys (scope-aware) registry (packages/dashboard/app/components/settings/section-keys.ts) drives per-menu reset via updateSettings/updateGlobalSettings with null-as-delete; non-blob sections (secrets, MCP, plugins, memory, auth, prompts, CLI agents, runtimes) are excluded with a documented reason.
|
||||
- 68f5153: summary: Add a per-workflow planner oversight level setting (Off, Observe, Steer, Autonomous recovery).
|
||||
category: feature
|
||||
dev: New workflow setting `plannerOversightLevel` declared in BUILTIN_OVERSIGHT_SETTINGS; default `autonomous`. Per-task override and engine behavior land in follow-up tasks.
|
||||
- aa757bc: summary: Tasks can override the workflow planner oversight level (Off, Observe, Steer, Autonomous recovery).
|
||||
category: feature
|
||||
dev: New nullable Task.plannerOversightLevel field (migration 137, SCHEMA_VERSION 137) mirroring executionMode; NULL inherits the workflow setting. Adds resolveEffectivePlannerOversightLevel precedence helper. Dashboard UI/API threading and engine behavior land in follow-up tasks.
|
||||
- 0689250: summary: Planner oversight now defaults to full steering/control for every workflow unless explicitly changed.
|
||||
category: feature
|
||||
dev: Confirms the `plannerOversightLevel` workflow-setting default is the highest (autonomous) level; unset workflow value and unset per-task override both resolve to full steering via `resolveEffectivePlannerOversightLevel` (task override → workflow effective value → autonomous), adding dedicated regression coverage for the "unless explicitly disabled" precedence.
|
||||
- 12a6d1b: summary: Planner oversight now monitors tasks across executor, reviewer, merger, pull-request, and workflow-gate stages.
|
||||
category: feature
|
||||
dev: Adds records-only PlannerOverseerMonitor + resolveWatchedStage + OverseerStageObservation in @fusion/engine, gated by resolveEffectivePlannerOversightLevel (off = no observation) and wired into ProjectEngine via a bounded poll. Steering/recovery and UI land in FN-7512/FN-7515+.
|
||||
- 81f2053: summary: Planner oversight can autonomously inject guidance, retry stuck/failed steps, and request fixes within bounded limits.
|
||||
category: feature
|
||||
dev: Adds pure `decidePlannerRecovery` + recovery types (core) and `PlannerRecoveryController` with injected guidance/retry/targeted-fix handlers (engine), consuming the FN-7511 observation. Acts only at effective level `autonomous`, caps attempts per (task, stage) via `PLANNER_RECOVERY_MAX_ATTEMPTS`, skips user-paused tasks, and excludes merge/PR/destructive actions (deferred to FN-7513) and comprehensive human-control safeguards (FN-7514).
|
||||
- 2cc84b5: summary: Planner oversight now requires confirmation before merge/PR actions and destructive/external side effects.
|
||||
category: feature
|
||||
dev: Adds `PlannerActionSideEffectClass` + `PlannerConfirmationRequest` and `classifyPlannerActionSideEffect`/`requiresPlannerConfirmation` (core), extends `decidePlannerRecovery` with an `await_confirmation` action, and adds `requestConfirmation`/`resolveConfirmation` gating to `PlannerRecoveryController` (engine). Merge/PR and destructive/external actions never execute without a recorded approval; bounded recovery (guidance/retry/targeted-fix) is unchanged. UX rendering, human-control safeguards, timeline, and run-audit land in follow-up tasks.
|
||||
- 79ab367: summary: Planner overseer now stays fully hands-off for paused tasks and auto-merge-off / human-review tasks.
|
||||
category: feature
|
||||
dev: Adds the pure `evaluateOverseerHumanControl` policy (packages/engine/src/overseer-human-control-policy.ts), consulted at the top of `PlannerRecoveryController.tick()` before any action classification, confirmation gating, steering, retry, or dispatch — so a user-paused or `autoMerge:false`/human-review task never even records a pending confirmation. Reuses `allowsAutoMergeProcessing` from `@fusion/core` verbatim (never re-derives the auto-merge/human-review predicate). Distinguishes explicit user pause (`task.userPaused===true`, or `task.paused===true` with no `pausedReason`) from engine/self-healing parks (which always stamp a `pausedReason`). Emits a bounded `overseer:oversight-withheld-human-control` run-audit no-action event (metadata: `{ taskId, reason, stage, oversightLevel }`), deduped per (taskId, reason) so it does not spam every poll.
|
||||
- c16cc9e: summary: Configure planner oversight level per task and per project in the workflow editor and task create/detail.
|
||||
category: feature
|
||||
dev: Per-task `plannerOversightLevel` override exposed via TaskForm (Inherit/off/observe/steer/autonomous), threaded through createTask/updateTask; workflow-editor Values tab gets a first-class display entry. Workflow-native setting; not a project setting.
|
||||
- aae603b: summary: Add a configurable planner-overseer notification verbosity level (Silent/Errors/Important/All).
|
||||
category: feature
|
||||
dev: New workflow-native enum setting `plannerOversightNotificationLevel` in BUILTIN_OVERSIGHT_SETTINGS; default `important`. Resolves via resolveEffectiveSettings; emission gating that reads it lands in FN-7519/FN-7520.
|
||||
- d10ea9a: summary: Add a task-detail planner-overseer intervention timeline (stage, reason, action, outcome, attempts, links).
|
||||
category: feature
|
||||
dev: New core `PlannerInterventionEntry` model + `recordPlannerIntervention`/`getPlannerInterventionTimeline` helpers persisting via the run-audit store under the `overseer:intervention` mutation, plus a `PlannerInterventionTimeline` component rendered in the task-detail Planner Oversight cluster. Emission call-sites land in FN-7520.
|
||||
- bf68839: summary: Emit planner-overseer run-audit events for observations, steering, retries, recovery, confirmations, and escalations.
|
||||
category: feature
|
||||
dev: New core emitters (emitOverseerObservation/Steering/RecoveryAttempt/Retry/Confirmation/Escalation) in planner-overseer-events.ts, each mapping its decision-point to the correct intervention action/outcome and delegating to FN-7519's recordPlannerIntervention under the overseer:intervention mutation. Producer call-sites land in FN-7511/FN-7512/FN-7513.
|
||||
- c4d81fe: summary: Add an AI-undo fallback task when reverting a done task via git conflicts or is unsupported.
|
||||
category: feature
|
||||
dev: `POST /api/tasks/:id/revert` now accepts `{ mode?: "git" | "ai" | "auto" }` (default `"auto"`). `"auto"` tries the FN-7523 git-revert path first and falls back to creating an AI-undo board task (`{ mode: "ai", createdTaskId, alreadyOpen? }`) on a conflicting or unsupported (e.g. workspace) git result; `needsHuman` (autoMerge-off) never triggers the fallback. `"ai"` always creates the AI-undo task; `"git"` keeps the FN-7523 git-only contract, which is otherwise unchanged. New engine exports: `createAiUndoTask`, `buildAiUndoTaskDescription`, `REVERT_OF_METADATA_KEY`. New core store method `TaskStore.findOpenRevertTaskForSource` backs the idempotency guard (an open undo task suppresses a duplicate; a closed one does not).
|
||||
- e7cb2f1: summary: Add a Revert action to Done/Archived task cards to undo landed changes.
|
||||
category: feature
|
||||
dev: Wires onRevertTask through Board/List/Detail surfaces; calls POST /tasks/:id/revert in "auto" mode with a conflict-confirm AI-undo fallback (mode: "ai").
|
||||
- 5ad8ec8: summary: Capture a structured performance snapshot when an agent task completes.
|
||||
category: feature
|
||||
dev: New AgentReflectionService.captureTaskPerformance persists a non-LLM post-task ReflectionMetrics record (duration, packages/files touched, verification command + scope, retry/rework count) and emits ids/counts-only `reflection:captured` run-audit telemetry; populates performanceSummary/latestReflection.
|
||||
- 726cbf8: summary: Task cards can now show the planner overseer's active state (idle/watching/steering/recovering/awaiting-confirmation).
|
||||
category: feature
|
||||
dev: Adds a serializable `PlannerOverseerRuntimeSnapshot` + pure `derivePlannerOverseerState` (core), a read-only `ProjectEngine.getPlannerOverseerRuntimeSnapshot(taskId)` accessor assembling it from the FN-7511 monitor + FN-7512/7513 recovery controller, and a best-effort additive `plannerOverseerState` enrichment on `GET /api/tasks` (mirrors the `branchProgress` pattern; never persisted, never fails the board load). Consumed by FN-7516's TaskCard.
|
||||
- 2ed06f9: summary: Support reverting multi-repo workspace tasks via git, all-or-nothing across sub-repos.
|
||||
category: feature
|
||||
dev: Extends `packages/engine/src/task-revert.ts` with `resolveWorkspaceTaskRevertCommits`/`revertWorkspaceTask` and wires `POST /api/tasks/:id/revert` to dispatch workspace tasks (`isWorkspaceTask`) to the new path; returns `{ mode: "git", clean, workspace: { repos: [...] }, conflicts? }`. Single-repo `performTaskRevert` path is unchanged.
|
||||
- 8c6f76c: summary: Add per-sha revert commit granularity to the task revert API and service.
|
||||
category: feature
|
||||
dev: `performTaskRevert` and `POST /api/tasks/:id/revert` accept an optional `granularity: "squash" | "per-sha"` (default `"squash"`, unchanged FN-7523 behavior). `"per-sha"` creates one attributed `revert(FN-xxxx)` commit per original sha (each with its own `Fusion-Task-Id` trailer and audit line), skipping no-op shas without empty commits. A mid-batch conflict in either mode rolls back the whole batch to the pre-call HEAD — no partially-landed per-sha commits. The clean result now reports `revertCommitShas: string[]` (all created commits) alongside the existing `revertCommitSha` (kept for backward compatibility).
|
||||
- f992e6a: summary: Add a dedicated Keyboard Shortcuts settings section with click-to-record capture and more configurable actions.
|
||||
category: feature
|
||||
dev: Relocates dashboardKeyboardShortcuts into its own settings section, adds a ShortcutCaptureInput recorder, and extends DashboardShortcutAction with openFiles/openSettings/openCommandCenter/newTask actions wired into existing App nav handlers.
|
||||
- 2df6c35: summary: Open a revert PR for done/archived tasks when autoMerge is disabled instead of refusing.
|
||||
category: feature
|
||||
dev: `POST /api/tasks/:id/revert` gains an additive `{ mode: "pr", clean: true, prUrl, prNumber, revertBranch, existingPr? }` result for clean single-repo reverts under `autoMerge:false`, reusing `GitHubClient.createPr`, `findPrForBranch` idempotency, and the `manual:true` PR handoff. New engine export `prepareRevertPrBranch` (packages/engine/src/task-revert.ts) prepares the dedicated `fusion/revert-<id>` branch without ever mutating the base branch. Existing `{ mode: "git" | "ai", ... }` shapes and the `autoMerge:true` path are unchanged.
|
||||
- 94e9d15: summary: AI-undo tasks now default to a configurable, stricter review workflow.
|
||||
category: feature
|
||||
dev: New project setting `aiUndoTaskWorkflowId` (default `builtin:review-heavy`) selects the workflow for AI-undo board tasks created by `POST /api/tasks/:id/revert` (`mode: "ai"`, the `auto` conflict fallback, and the workspace conflict fallback all share the `createAiUndoResult()` closure, so all three inherit this default). A blank/unset value means the created task inherits the project default workflow (pre-FN-7556 behavior). The route validates the configured id via `getWorkflowDefinition`/`isBuiltinWorkflowId` and falls back to inherit (with a logged warning) on a blank or unknown value, so a misconfigured id never breaks AI-undo task creation. The engine's `createAiUndoTask` helper stays pure — it only forwards a `workflowId` it is given, never resolves the setting itself. The Settings Modal UI field for this setting is a deliberate follow-up task; the value is settable today only via the settings API.
|
||||
- 3dd227b: summary: Plan auto-approval is now the default; specified tasks skip manual approval unless you opt into workflow/require-all.
|
||||
category: feature
|
||||
dev: `DEFAULT_PROJECT_SETTINGS.planApprovalMode` flips `workflow` → `auto-approve-all`; existing projects with an explicit stored value are unchanged; consumed by `resolvePlanApprovalRequired` at the triage gating sites.
|
||||
- 78d4db9: summary: Fusion self-repo issue-close comments now show current and target release versions.
|
||||
category: feature
|
||||
dev: GitHubIssueCommentService appends "Current version: v{current}" and "Target release: v{next-minor}" lines when the linked source issue is runfusion/fusion; other repos unchanged. Version resolved via getCliPackageVersion.
|
||||
- 7435849: summary: Open one revert PR per sub-repo for workspace tasks when autoMerge is disabled.
|
||||
category: feature
|
||||
dev: `POST /api/tasks/:id/revert` gains an additive workspace `{ mode: "pr", clean: true, workspace: { repos: [{ repo, revertBranch, prUrl, prNumber, existingPr? }] } }` result for clean multi-repo reverts under `autoMerge:false`, extending FN-7554's single-repo `mode:"pr"` path. New engine export `prepareWorkspaceRevertPrBranches` (packages/engine/src/task-revert.ts) classifies every sub-repo first and only prepares a dedicated `fusion/revert-<id>` branch per sub-repo when all are clean/already-reverted (all-or-nothing at the branch-prep phase), never force-writing any sub-repo integration branch. The route resolves owner/repo and checks the rate limiter for every sub-repo before pushing/creating any PR, so GitHub-unconfigured/rate-limited cases degrade the whole task to `needsHuman` rather than opening a partial subset of PRs. Existing `{ mode: "git" | "ai" | "pr", ... }` shapes, the `autoMerge:true` workspace path, and FN-7554's single-repo path are unchanged.
|
||||
- 73b38ba: summary: Add a Settings → General picker to choose the workflow used for AI-undo (revert) tasks.
|
||||
category: feature
|
||||
dev: Surfaces `aiUndoTaskWorkflowId` (default `builtin:review-heavy`) in GeneralSection; empty selection means "inherit project default workflow", matching the revert route's blank-is-inherit behavior from FN-7556.
|
||||
- 42bbe58: summary: Add "Ask user question" and "Exit gate" workflow nodes for mid-flow chat reach-out and early exit.
|
||||
category: feature
|
||||
dev: New IR node kinds `ask-user` (reuses await-input park/resume; surfaces the question in the task chat) and `exit-gate` (terminates the workflow early, optional condition). Editor palette + summaries + help updated; `prompt`+`awaitInput` remains a back-compat alias.
|
||||
- 53fe0d7: summary: Add a built-in "Brainstorming" workflow that talks to you before planning.
|
||||
category: feature
|
||||
dev: Registers `builtin:brainstorming` (non-default, default-enabled) composing FN-7579's `ask-user` → refine → `exit-gate`-on-approval phase ahead of the normal coding plan/execute/review/merge spine. Parity suite (`builtin-workflows.test.ts`) extended for the new entry.
|
||||
- ecbbb29: summary: Add a "Coding (Ideas)" workflow with a manual Ideas intake and a merged Todo planner column.
|
||||
category: feature
|
||||
dev: New `builtin:coding-ideas` clones the default stepwise pipeline with an `ideas` intake (autoTriage:false) in front of a merged `todo` planner+capacity column. createTask lands cards in the workflow's intake column; the triage service plans unplanned todo tasks in place; the scheduler skips bootstrap-prompt todo tasks; TaskCard gains a Start button and a Ready badge.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8668a05: summary: Add a workflow setting to disable automatic large-task triage splitting.
|
||||
category: feature
|
||||
dev: Adds triageProactiveSubtaskSplittingEnabled while preserving explicit breakIntoSubtasks requests.
|
||||
- 978cdda: summary: Show active Plan Review progress on triage task cards.
|
||||
category: fix
|
||||
dev: TaskCard now renders the existing progress affordance for Triage only when unified progress has active workflow work.
|
||||
- 635fca2: summary: Remove the eye icon markdown/plain toggle from chat; messages always render as Markdown.
|
||||
category: breaking
|
||||
dev: Removed ChatView `chat-thread-header-render-toggle` (desktop + mobile), `showAllAsPlain` state, and `chat.showRenderedMarkdown`/`chat.showPlainText` i18n keys (FN-7541).
|
||||
- 3d55102: summary: Clarify task-detail oversight Nudge/Explain controls: visible label, disabled reason, always-openable Explain panel.
|
||||
category: fix
|
||||
dev: TaskDetailModal now renders a `detail-oversight-controls-label` group label and `detail-overseer-nudge-disabled-reason` helper text (both gated by the existing oversight-cluster visibility condition); Explain no longer disables on `!canExplainOverseer` since it is read-only. Nudge's `canNudgeOverseer` gate and Stop's confirm dialog are unchanged.
|
||||
- 0f1cd0a: summary: Unify border, radius, and height of the task-detail Priority/Execution/Oversight controls.
|
||||
category: fix
|
||||
dev: Adds a shared --detail-control-border-radius token alongside --detail-priority-control-min-height so .detail-priority-chip, .detail-execution-mode-toggle, .detail-oversight-chip, and .detail-oversight-menu-trigger all resolve the same border-width/color/radius/height.
|
||||
- b42ba9f: summary: Keep the task-detail Activity view menu open during mobile iOS taps.
|
||||
category: fix
|
||||
dev: Guards the Activity views dropdown against iOS visualViewport resize/scroll echoes during menu opening.
|
||||
- a7559b0: summary: Fix Anthropic subscription login when pasted callback URLs contain fragment OAuth parameters.
|
||||
category: fix
|
||||
dev: Normalizes pasted OAuth callback fragments before resolving dashboard manual-code login prompts.
|
||||
- 4b530a6: summary: Fix Claude/Anthropic subscription re-login showing "Login did not complete" after logging out.
|
||||
category: fix
|
||||
dev: Anthropic subscription OAuth is aliased across the legacy `anthropic` row (where interactive login persists the credential) and the `anthropic-subscription` id (where the settings card's in-memory logged-out suppression and status read are keyed). Re-login wrote only `anthropic`, so `loggedOutProviders` kept suppressing `anthropic-subscription` and the card reported failure despite a valid stored credential until process restart. auth-storage's proxy now clears the logged-out state on both aliases when either is re-authenticated (new `login` trap + hardened `set` trap via `clearReauthenticatedLogoutState`; raw api_key writes stay scoped to their own card). Also surfaces background OAuth login failures on `GET /auth/status` (`loginError`) + server logs so future paste-callback failures are diagnosable instead of a generic error.
|
||||
- a5ac3c3: summary: Stop self-healing from killing actively-running tasks after ~30 minutes.
|
||||
category: fix
|
||||
dev: FN-7566. isPhantomExecutorBinding's liveness gate (heartbeat/checkout/runAudit) was blind to ephemeral executor agents, leaving only the age>graceMs\*3 threshold, so any ephemeral-executor task running longer than ~30 min was reclaimed to `todo` mid-flight. Adds the in-process live-session veto (activeSessionRegistry path / executingTaskLock / isTaskActive), mirroring the isWorkspaceTaskLive/sessionDead predicate, and honors clearPhantomExecutorBinding's live-session refusal in reclaimSelfOwnedBranchConflicts.
|
||||
- 8912399: summary: Stop Windows Terminal version dialogs from popping up when opening the dashboard or Settings on Windows.
|
||||
category: fix
|
||||
dev: Root cause was the worktrunk integration, not the embedded terminal: worktrunk's CLI is named `wt`, which collides with Windows Terminal (`wt.exe`) on PATH, so probing it with `wt --version` launched Windows Terminal. Fixed by (1) `useWorktrunkInstallStatus` only auto-fetching `/api/worktrunk/status` when the integration is enabled (user opt-in) instead of on every Settings/dashboard mount, and (2) an engine-level guard in `probeWorktrunk` that refuses to exec a resolved `wt` that is the Windows Terminal alias (under `WindowsApps` / a `WindowsTerminal` package dir), covering all resolution surfaces.
|
||||
- b800f7d: summary: Select newly created folders automatically during project setup.
|
||||
category: fix
|
||||
dev: Adds DirectoryPicker opt-in selection for project-registration surfaces while preserving default picker behavior.
|
||||
- 9dc248e: summary: Prevent Desktop update banners from using 0.0.0 as the current version.
|
||||
category: fix
|
||||
dev: Dashboard update checks now resolve packaged @fusion/desktop metadata and fail closed for unresolved versions.
|
||||
- 52dbc0e: summary: Quit Fusion Desktop on Windows when the window is closed.
|
||||
category: fix
|
||||
dev: Updates Electron close lifecycle so Windows shutdown reaches embedded runtime cleanup.
|
||||
- ced783e: summary: Open desktop Anthropic Subscription OAuth logins in the system browser.
|
||||
category: fix
|
||||
dev: Adds Electron window-open policy coverage and preserves Settings auth polling completion paths.
|
||||
- 50786f2: summary: Delay GitHub setup warnings for one day and add a dashboard connect action.
|
||||
category: fix
|
||||
dev: Dashboard setup warnings now gate GitHub prompts per project and route the CTA to Settings → Authentication.
|
||||
- b4b1f6d: summary: Fix a false AI engine not running banner in desktop mode.
|
||||
category: fix
|
||||
dev: Distinguishes transient embedded desktop engine startup from true dashboard-only mode.
|
||||
- e8b7362: summary: Clarify the desktop Connection Manager add-remote flow.
|
||||
category: fix
|
||||
dev: Desktop Connection Manager now separates Local Server context from saved remote profiles and collapses the remote editor until add/edit.
|
||||
- 0900a38: summary: Restore Local Server in the desktop Switch server list.
|
||||
category: fix
|
||||
dev: Desktop Connection Manager now lists local and saved remote destinations together.
|
||||
- a2b09f2: summary: Make right-dock task list clicks respect the task popup setting.
|
||||
category: fix
|
||||
dev: Threads openMobileTasksInPopup through the right-dock Tasks list route while preserving embedded dock detail when disabled.
|
||||
- 0f05156: summary: Auto-retry retryable Code Review remediation failures.
|
||||
category: fix
|
||||
dev: Prevents retryable code-review-remediation graph failures from stranding tasks in in-review.
|
||||
- 20184ac: summary: Fix no-op task branch recovery after a previously landed task.
|
||||
category: fix
|
||||
dev: Merge/recovery ownership classification now checks no-diff branches before foreign trailer rejection.
|
||||
- 82493e0: summary: Allow documented source-free task-artifact deliveries to finish without commits.
|
||||
category: fix
|
||||
dev: fn_task_done now recognizes explicit gitignored .fusion/tasks artifact contracts while preserving source-change no-commit guards.
|
||||
- 5689346: summary: Fix direct merges so Push to remote after merge honors the configured remote and branch.
|
||||
category: fix
|
||||
dev: Resolves remote-only push targets from the merge integration branch and preserves non-fatal push errors on done tasks.
|
||||
- b42be87: summary: Keep task popups on the board layer with Activity menus above them.
|
||||
category: fix
|
||||
dev: Task-detail FloatingWindow callers use a lower layer band, and Activity view menus reposition after popup geometry changes.
|
||||
- 61c8bdc: summary: Keep accepted chat requests waiting instead of showing false first-event timeout failures.
|
||||
category: fix
|
||||
dev: Dashboard chat POST streams no longer abort accepted-but-silent responses on the client first-event timer.
|
||||
- e8dc2ae: summary: Show each task's original prompt in the Plan tab alongside the generated plan.
|
||||
category: fix
|
||||
dev: Adds a read-only Task Detail original-prompt section backed by task.description.
|
||||
- d2e3134: summary: Add before-to-after transformation summaries to generated task definitions.
|
||||
category: feature
|
||||
dev: Built-in standard and fast triage prompts now require a `## Before → After Transformation` section.
|
||||
- b0208c1: summary: Restore terminal Ctrl/Cmd copy and paste shortcuts.
|
||||
category: fix
|
||||
dev: Integrated and embedded terminals now own physical clipboard paste to avoid swallowed or duplicate input.
|
||||
- 2797803: summary: Show first-token and tool processing durations in task agent logs.
|
||||
category: feature
|
||||
dev: Adds optional agent-log timing fields `timeToFirstTokenMs` and `durationMs`.
|
||||
- a2d6349: summary: Fix mobile Chat composer being hidden behind the keyboard accessory bar.
|
||||
category: fix
|
||||
dev: Adds keyboard-open bottom clearance in ChatView so the composer clears the iOS input-assistant/autofill bar without a persistent .chat-thread transform or Android reserved-gap.
|
||||
- 4baa4c4: summary: Settings descriptions now show each setting's default value.
|
||||
category: feature
|
||||
dev: Appended default-value copy to settings.\* i18n descriptions across Global, Runtimes, and Project Settings sections, sourced from DEFAULT_GLOBAL_SETTINGS/DEFAULT_PROJECT_SETTINGS in settings-schema.ts; added settings-default-descriptions.test.tsx guarding that every surfaced setting states a default (or explicit "inherits"/"no default \u2014 unset") and that every DEFAULT_SETTINGS key is documented or allowlisted as not surfaced.
|
||||
- 53d7b7e: summary: Add an intelligent git-revert engine service and POST /api/tasks/:id/revert route.
|
||||
category: feature
|
||||
dev: New `packages/engine/src/task-revert.ts` exports `resolveTaskRevertCommits`, `classifyTaskRevert`, and `performTaskRevert` (squash/rebase/lineage attribution precedence, dry-run classification, guaranteed-clean rollback). Route enforces done/archived-only and autoMerge-off guard rails; conflicting results are returned unresolved for sibling FN-7524 (AI-undo) to act on. Workspace tasks return `unsupported`.
|
||||
- 4707eb5: summary: Auto-approve now reliably sends specified plans to the board without a manual approval stop.
|
||||
category: fix
|
||||
dev: FN-7526 — investigated the reported "plans still park at awaiting-approval when auto-approve is on" symptom; resolvePlanApprovalRequired, mergeEffectiveSettings/applyWorkflowSettingsOverlay, and every finalizeApprovedTask call site (specifyTask, recoverApprovedTask, retryUnavailablePlanReview, tryFinalizeExplicitDuplicateMarker) already honored project planApprovalMode: "auto-approve-all" over a stored workflow requirePlanApproval value — no production defect reproduced. Added end-to-end regression coverage across every enumerated surface (Plan Review reviewer-outage retry, refinement routing, self-healing starved-refinement recovery) using the real mergeEffectiveSettings pipeline instead of isolated bare-settings unit calls, plus explicit assertions that the independent release-authorization and Workflow Plan Review gates remain intact under auto-approve-all, so a future bare-settings call site is caught immediately instead of silently reintroducing the reported behavior.
|
||||
- 3b52a4d: summary: Fix the in-dashboard Switch server menu not switching desktop local/remote.
|
||||
category: fix
|
||||
dev: The desktop shell's redirect effects in App.tsx read a dead `localServer` field that the preload never populates; extracted `resolveDesktopShellRedirectTarget` in appLifecycle.ts now derives the navigation target from the live `localRuntime`/`activeProfileId` state for both directions, and the unused `localServer` field was removed from `ShellConnectionState`.
|
||||
- 36bd74e: summary: Fix branch group completion checklists to show accurate landed/finished counts.
|
||||
category: fix
|
||||
dev: runAiMerge (the sole merge path since master-plan U0) never resolved branch-group routing or stamped mergeDetails.mergeTargetBranch/mergeTargetSource, so isBranchGroupMemberLanded permanently reported shared-group members as not landed. Routes through resolveBranchGroupMergeRouting (matching the legacy merger.ts pattern) and stamps the target fields on both the landed and no-op finalize paths; preserves merge-target-safety in isBranchGroupMemberLanded (a sibling/mismatched-branch member still never counts as landed).
|
||||
- df0be88: summary: Branch groups no longer report complete (or become promotable) when an unlanded member is archived.
|
||||
category: fix
|
||||
dev: listTasksByBranchGroup membership now scans with includeArchived:true so an archived-but-unlanded member stays counted in total instead of silently dropping out; mergeDetails is now persisted on ArchivedTaskEntry so an archived member that had already landed keeps counting as landed. evaluateBranchGroupCompletion / promoteBranchGroup gate correctly; merge-target-safety in isBranchGroupMemberLanded is unchanged.
|
||||
- ec9ac61: summary: Fix the global GitLab integration setting not persisting when saved.
|
||||
category: fix
|
||||
dev: splitSettingsSave now diffs the five global GitLab keys (gitlabEnabled, gitlabInstanceUrl, gitlabApiBaseUrl, gitlabAuthToken, gitlabAuthTokenType) against scoped global initials only, never the project-effective merged initialValues, so a project override no longer suppresses a real global save.
|
||||
- 8d36b99: summary: Fix task-detail Activity view dropdown not opening reliably on mobile.
|
||||
category: fix
|
||||
dev: Guards the Activity menu's window resize/orientationchange/scroll close-listener with the same opening-tap timing guard already used for visualViewport, and exempts scroll events originating in the `.detail-tabs` scroller, so a same-gesture mobile tap echo (Android/iOS, fixed modal or `.floating-window--task-detail` popup) no longer closes the menu the instant it opens.
|
||||
- ad744aa: summary: Manual "Run now" for the Database Backup automation now runs in-process like the scheduler, matching cron behavior.
|
||||
category: fix
|
||||
dev: The legacy single-command and command-step manual automation run path (`executeSingleCommand` in packages/dashboard/src/routes.ts) now intercepts `isInProcessBackupCommand`/`isInProcessMemoryBackupCommand` via the scoped TaskStore, mirroring `RoutineRunner.executeCommand`/`CronRunner`, instead of always shelling out via `exec()`. `formatInProcessBackupError`, `isInProcessBackupCommand`, and `isInProcessMemoryBackupCommand` are now exported from `@fusion/engine` for reuse. Existing onStep/onText live-run callbacks already stream incremental output for command/backup runs; added regression coverage confirming this holds for the new interception branch.
|
||||
- 5c3d58a: summary: Task cards no longer show the "Auto-recovery" oversight badge unless oversight is explicitly configured.
|
||||
category: fix
|
||||
dev: `TaskCard.tsx`'s `showOversightBadge` gate now also suppresses the badge when the effective level equals `DEFAULT_PLANNER_OVERSIGHT_LEVEL` ("autonomous") and there is no explicit per-task `plannerOversightLevel` override; an explicit per-task override of "autonomous" still renders the badge.
|
||||
- b4be515: summary: Remove the per-card overseer-state ("Executor") badge from task cards.
|
||||
category: fix
|
||||
dev: Deleted the FN-7516 `card-overseer-state-badge` render, its card-local `deriveOverseerCardWatchedStage` helper/label maps, and its CSS; the sibling oversight-level badge (`card-oversight-badge`) is unaffected.
|
||||
- 62ddb19: summary: Original task prompt now renders as Markdown and is collapsed by default in the task Plan tab.
|
||||
category: feature
|
||||
dev: Task Detail Plan/Definition tab original-prompt section reuses the existing `.detail-source-toggle`/`.detail-source-chevron--expanded` collapse pattern and the shared `ReactMarkdown` pipeline (`remarkGfm`, `sharedRehypePlugins`, `markdownLinkifyComponents`); backed by read-only `task.description`, no change to the generated `PROMPT.md` editor/revision flow.
|
||||
- 883c73e: summary: Fix agent-created artifacts not appearing live in the dashboard artifacts view.
|
||||
category: fix
|
||||
dev: Root cause was cross-instance artifact-registration replication, not the route/hook/render path (all already correct). `TaskStore.registerArtifact()` never bumped `lastModified`, and `checkForChanges()` (the polling replicator that lets a second TaskStore instance on the same project — e.g. the dashboard's cached store vs. the engine's own store — mirror events it did not write itself) only ever diffed the `tasks` table, never `artifacts`. A store instance that did not perform the write could therefore never observe or re-emit `artifact:registered`, leaving an already-open Documents/task Artifacts gallery stale until a full reload. Fixed by bumping `lastModified` on artifact writes and adding a strictly-increasing `rowid`-cursor poll over the `artifacts` table in `checkForChanges()`. See `packages/core/src/__tests__/artifacts.test.ts` and `packages/dashboard/src/routes/__tests__/artifacts-route-integration.test.ts` for regression coverage.
|
||||
- d09b57f: summary: Fix the mobile terminal shortcut bar so it scrolls horizontally to reach every key.
|
||||
category: fix
|
||||
dev: Added `min-width: 0` to `.terminal-shortcut-panel` to defeat the flex min-width:auto trap that clipped overflow instead of engaging `overflow-x: auto`.
|
||||
- 3d58260: summary: Planner-oversight intervention timeline now populates from real engine activity.
|
||||
category: fix
|
||||
dev: Wires PlannerOverseerMonitor/PlannerRecoveryController decision points to the FN-7520 emitOverseer\* façade with the real TaskStore; observation/escalation emission deduped per (task, stage[, signal]).
|
||||
- 052a277: summary: Show the "Global" prefix on the Authentication entry in the mobile Settings picker.
|
||||
category: fix
|
||||
dev: resolveSettingsSectionOptionLabel now derives the Global-group prefix for storage-less (scope: undefined) sections in SettingsModal.tsx (FN-7552).
|
||||
- 6e4c207: summary: Tasks held for release authorization or Plan Review are now shown distinctly, so auto-approve no longer looks broken.
|
||||
category: fix
|
||||
dev: FN-7559 — auto-approve-all bypasses only the manual plan-approval gate (unchanged, FN-7526). Release-authorization holds are surfaced with a new distinct status reason (`Task.awaitingApprovalReason: "release-authorization"`) and no longer render the generic manual Approve/Reject affordance in TaskCard/TaskDetailModal; Workflow Plan Review already used distinct statuses (`needs-replan`/`plan-review-unavailable`) and is unaffected. Both gates remain independent and intact — this is UI/data disambiguation only.
|
||||
- 6d364fc: summary: Move mobile terminal controls into a bottom footer so they no longer crowd the header, with a scrollable shortcut bar.
|
||||
category: fix
|
||||
dev: On the ≤768px terminal, the `.terminal-actions` cluster now renders in a `terminal-footer-actions` bar (with `min-width:0; overflow-x:auto`) instead of the header; desktop/floating/pinned-below keep the FN-7502 header layout. Preserves the FN-7550 shortcut-panel scroll fix.
|
||||
- b471aec: summary: Stop the release-authorization gate from holding tasks that merely disclaim releasing.
|
||||
category: fix
|
||||
dev: classifyReleaseTask now strips negated release-disclaimer clauses (e.g. "this task performs no release/publish; releases are owned by scripts/release.mjs") before signal matching in packages/engine/src/triage-release-authorization.ts, so revert/undo/UI specs are no longer false-flagged as release-class. Genuine "run pnpm release"/"publish @runfusion/fusion" intent still trips the gate.
|
||||
- 9d4a45b: summary: Fix mobile terminal text still rendering with excess inter-character gaps after font-load settle.
|
||||
category: fix
|
||||
dev: Root cause: xterm's OptionsService setter is a no-op when reassigning an already-current fontFamily/fontSize, so post-settle reapply never forced CharSizeService/DomRenderer to remeasure. Added `forceTerminalFontRemeasure()` in `terminalPreferences.ts`, used by both `TerminalModal.tsx` and `SessionTerminal.tsx` at every post-`waitForTerminalFontMetrics()` settle site.
|
||||
- 72b77bf: summary: Stop Plan Review from looping tasks forever and fix its "can't find the plan" reviews.
|
||||
category: fix
|
||||
dev: FN-7561 — Plan Review pre-merge gate hardening in packages/engine/src/executor.ts. (1) The reviewer ran readonly with cwd=worktree but the spec lives at project-root .fusion/tasks/<id>/PROMPT.md, so "Read PROMPT.md" produced "no PROMPT.md found / data is in a DB" non-verdicts; the spec text is now injected into the reviewer prompt via readTaskArtifact. (2) A malformed reviewer response now self-retries once on the primary model when no fallback is configured. (3) A malformed (advisory_failure, no verdict) plan-review result can never trigger a triage replan. (4) The unbounded plan-review replan default is capped at 15 attempts with a loud halting log entry, so a persistently-disagreeing planner/reviewer no longer burns LLM calls indefinitely (FN-7525 ran 13+ attempts overnight).
|
||||
- c08498e: summary: Planner-overseer task badge now shows a readable label and explains what it is waiting on.
|
||||
category: fix
|
||||
dev: TaskCard badge renders plannerOverseerStateLabel + plannerOverseerBadgeTooltip built from the existing PlannerOverseerRuntimeSnapshot (reason/watchedStage/signal/pendingConfirmation); presentation-only, no engine changes.
|
||||
- 24b27e8: summary: Plan approve/reject API now blocks release-authorization holds, requiring the authorization marker first.
|
||||
category: fix
|
||||
dev: FN-7564 — POST /tasks/:id/approve-plan and /reject-plan now return 400 when task.awaitingApprovalReason === "release-authorization" (FN-7559 discriminator), enforcing the FN-6481 release-authorization gate at the API layer regardless of client. Manual-approval holds are unaffected.
|
||||
- fb45157: summary: Pin the mobile terminal close (X) button to the top-right corner so it is easy to find and tap.
|
||||
category: fix
|
||||
dev: On the ≤768px terminal, the `terminal-close` button now carries a `terminal-close--corner` class (order:3 + margin-inline-start:auto) so it renders last in flex order and hugs the right edge next to the tab dropdown, instead of falling back to order:0 (far left). Desktop/floating/pinned-below placement inside `.terminal-actions` is unchanged.
|
||||
- 7c0be53: summary: Fix mobile terminal excess character spacing that survived earlier font-remeasure fixes.
|
||||
category: fix
|
||||
dev: `TerminalModal`/`SessionTerminal` re-bake xterm's `DomRenderer` letter-spacing compensation AFTER `fitAddon.fit()` settles the post-fit column count (not just before it), since `handleResize()` never re-bakes spacing itself. See `docs/solutions/ui-bugs/xterm-options-noop-remeasure-after-font-settle.md` recurrence #4.
|
||||
- 71dfd3a: summary: Rename downloadable CLI release binaries to the fn-cli-<platform> base name.
|
||||
category: internal
|
||||
dev: `binaryNameForTarget` in `packages/cli/build.ts` and the `release.yml` / `test-release.yml` matrices now emit `fn-cli-<suffix>`; the local dev binary stays `fn`/`fn.exe`.
|
||||
- 9592e3a: summary: Manual plan approval no longer re-asks you to approve a plan you already approved when it hasn't changed.
|
||||
category: fix
|
||||
dev: FN-7569 — approving a plan records a fingerprint of the approved PROMPT.md (new nullable Task.approvedPlanFingerprint, migration 139). The manual plan-approval gate skips re-parking at awaiting-approval when a re-specification (replan, plan-review retry, self-healing rebound) produces the same plan; a changed plan or reject-plan still requires fresh approval. Release authorization, Workflow Plan Review, and auto-approve-all are unchanged.
|
||||
- c31f9ef: summary: Move the planner intervention timeline into the task Activity view dropdown.
|
||||
category: feature
|
||||
dev: Removes the inline `PlannerInterventionTimeline` mount from the FN-7517 oversight cluster in `TaskDetailModal.tsx` and adds a fourth `interventions` `ActivitySegment`, shown in the Activity dropdown only when planner oversight is active for the task; falls back to Live if oversight turns off while Interventions is selected.
|
||||
- ce9df29: summary: Expired Claude subscription logins now show disconnected with a re-login prompt; tokens auto-refresh before expiry.
|
||||
category: fix
|
||||
dev: Unifies OAuth expiry detection between OAuthExpiryMonitor and /api/auth/status, and adds an engine-side proactive OAuth refresh scheduler wired in project-engine (guarded by skipNotifier). No token material logged.
|
||||
- 196abb5: summary: Anthropic subscription reads now refresh the OAuth token automatically instead of silently failing when expired.
|
||||
category: fix
|
||||
dev: mergeAuthStorageReads getApiKey("anthropic-subscription") now delegates to the underlying engine authStorage.getApiKey (the only refresh-token HTTP round trip) instead of a local static expiry check; regression tests drive the wrapper directly. No token material logged.
|
||||
- 45e5a26: summary: Stop GitHub tracking-issue creation from linking new tasks to old/closed issues.
|
||||
category: fix
|
||||
dev: github-tracking dedup now only reuses OPEN issues and requires a File-Scope path overlap (keyword-only matches no longer link). Prevents mis-linking a fresh task to a stale/resolved tracking issue (FN-7579). Setting `githubTrackingDedupEnabled` unchanged.
|
||||
- a1a6b09: summary: Clarify the oversight "Nudge unavailable" guideline so it no longer reads as an overseer fault.
|
||||
category: fix
|
||||
dev: TaskDetailModal oversight controls — reworded taskDetail.oversight.nudgeDisabledTitle and added taskDetail.oversight.nudgeSuppressedTitle to differentiate periodic-observation vs. manual-control states. No enablement/engine logic changed.
|
||||
- cf3fe8b: summary: New tasks created under the Coding (Ideas) workflow now land in the Ideas column and wait for you to promote them.
|
||||
category: fix
|
||||
dev: Dashboard create surfaces (InlineCreateCard, QuickEntryBox, NewTaskModal, insight/todo → task) no longer hard-code column:"triage"; the store now resolves the selected/default workflow's intake column. InlineCreateCard forwards workflowId at create time instead of applying it post-create. Also fixed a glue-layer regression in `useTaskHandlers.ts` (`handleBoardQuickCreate`/`handleModalCreate`) that re-forced column:"triage" even after the UI surfaces stopped sending it.
|
||||
- 8b4e522: summary: Fix tasks vanishing from the board after being added to a workflow like Coding (Ideas).
|
||||
category: fix
|
||||
dev: Board.tsx forces a board-workflows refetch (deferred one tick, signature-guarded) whenever a rendered task is missing from the taskWorkflowIds map, so its real workflow and intake column resolve regardless of which create surface added it; the single-workflow grouping also re-homes a task whose column its workflow no longer declares into the intake lane instead of dropping it. Fixes the FN-7591 regression where intake-column cards (column "ideas") fell back to the default workflow, which has no such column, and were filtered out until a manual reload.
|
||||
- f30d55f: summary: Move the Before → After transformation summary to the top of generated task definitions.
|
||||
category: fix
|
||||
dev: Reorders the standard and fast triage `PROMPT.md` templates in packages/core/src/agent-prompts.ts so `## Before → After Transformation` is the first content section, ahead of `## Review Level` and `## Mission`, matching FN-7499's glance-verification intent.
|
||||
- 20379e8: summary: Task-detail Priority dropdown now matches the Oversight dropdown's size, border, and typography.
|
||||
category: fix
|
||||
dev: Removed the Priority-only forced select/option uppercase, added a neutral chip background scoped to `.detail-priority-chip.card-priority-badge--normal` for the untinted `normal` level, and reused the FN-7585 shared `--btn-border-width`/`--border`/`--detail-control-border-radius`/`--detail-priority-control-min-height` tokens so both dropdowns render as one control style across desktop and the mobile oversight-overflow surface.
|
||||
- e0f3d3d: summary: Default workflow boards now label the intake column "Planning" instead of "Triage".
|
||||
category: fix
|
||||
dev: Renamed the `name` of the `id: "triage"` intake column to "Planning" in builtin-coding, builtin-stepwise-coding, and builtin-pr workflow IRs (column id unchanged; linear built-ins inherit via canonicalBuiltinWorkflowColumns). COLUMN_LABELS.triage was already "Planning".
|
||||
- 5b193d2: summary: Fix the task-detail Nudge control staying disabled when the overseer is actively watching.
|
||||
category: fix
|
||||
dev: GET /api/tasks/:id now attaches the transient plannerOverseerState snapshot (mirrors the list route); TaskDetailModal reads the snapshot from workingTask so detail refetches no longer drop it.
|
||||
- 546ef16: summary: Honor mission branchStrategy when triage omits branchAssignment; skip validation for inactive missions.
|
||||
category: fix
|
||||
dev: resolveBranchAssignmentContext returns undefined for absent mode so triage falls back to mission.branchStrategy; processTaskOutcome gates on mission.status === "active" like recoverActiveMissions.
|
||||
- b173f76: summary: Planner overseer no longer marks healthy in-progress tasks as "recovering" or steers them.
|
||||
category: fix
|
||||
dev: `decidePlannerRecovery` now returns `none` for healthy (`progressing`/`complete`) and `awaiting-human` executor/workflow-gate signals instead of falling through to `inject_guidance`; only `stuck`/`blocked`/`failed` trigger autonomous steering. Also dedupes the `PlannerOverseerMonitor` activity-feed heartbeat so an unchanged `(stage, signal, reason)` observation is logged once per change, not every poll tick. Fixes the "overseer recovering" badge appearing on every autonomous card and the needless AI-consuming guidance injections (FN-7577).
|
||||
|
||||
## 0.55.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -108,13 +108,19 @@ function targetToPrebuildName(target: BunTarget): string {
|
||||
|
||||
/**
|
||||
* Map a Bun target identifier to the output binary name.
|
||||
* e.g. "bun-linux-x64" → "fn-linux-x64", "bun-windows-x64" → "fn-windows-x64.exe"
|
||||
* e.g. "bun-linux-x64" → "fn-cli-linux-x64", "bun-windows-x64" → "fn-cli-windows-x64.exe"
|
||||
*
|
||||
* FNXC:Release 2026-07-04-00:00:
|
||||
* GitHub Release CLI assets use the `fn-cli-` base name (not `fn-`) so the
|
||||
* downloadable binary doesn't collide with other well-known `fn` tools on a
|
||||
* user's PATH. The local dev binary (defaultBinaryName) intentionally stays
|
||||
* `fn`/`fn.exe` — this rename only affects cross-compiled release assets.
|
||||
*/
|
||||
function binaryNameForTarget(target: BunTarget): string {
|
||||
// "bun-linux-x64" → "linux-x64"
|
||||
const suffix = target.replace(/^bun-/, "");
|
||||
const isWindows = target.includes("windows");
|
||||
return `fn-${suffix}${isWindows ? ".exe" : ""}`;
|
||||
return `fn-cli-${suffix}${isWindows ? ".exe" : ""}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@runfusion/fusion",
|
||||
"version": "0.55.0",
|
||||
"version": "0.56.1",
|
||||
"license": "MIT",
|
||||
"description": "Fusion CLI: HTTP API server, daemon, dashboard launcher, and task tooling for the Fusion AI coding agent.",
|
||||
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
||||
|
||||
@@ -25,7 +25,7 @@ function hasKnownBunSqliteLimitation(result: { stdout: string; stderr: string })
|
||||
function expectedBinaryName(target: string): string {
|
||||
const suffix = target.replace(/^bun-/, "");
|
||||
const isWindows = target.includes("windows");
|
||||
return `fn-${suffix}${isWindows ? ".exe" : ""}`;
|
||||
return `fn-cli-${suffix}${isWindows ? ".exe" : ""}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,7 +51,7 @@ const SHOULD_RUN_BUILD_EXE =
|
||||
|
||||
describe.skipIf(!SHOULD_RUN_BUILD_EXE)("build-exe-cross: single target", () => {
|
||||
beforeAll(() => {
|
||||
const bin = join(distDir, "fn-linux-x64");
|
||||
const bin = join(distDir, "fn-cli-linux-x64");
|
||||
if (existsSync(bin)) return;
|
||||
execSync("bun run build.ts --target bun-linux-x64", {
|
||||
cwd: cliRoot,
|
||||
@@ -60,8 +60,8 @@ describe.skipIf(!SHOULD_RUN_BUILD_EXE)("build-exe-cross: single target", () => {
|
||||
});
|
||||
}, 180_000);
|
||||
|
||||
it("produces dist/fn-linux-x64", () => {
|
||||
const bin = join(distDir, "fn-linux-x64");
|
||||
it("produces dist/fn-cli-linux-x64", () => {
|
||||
const bin = join(distDir, "fn-cli-linux-x64");
|
||||
expect(existsSync(bin)).toBe(true);
|
||||
expect(statSync(bin).size).toBeGreaterThan(0);
|
||||
});
|
||||
@@ -73,7 +73,7 @@ describe.skipIf(!SHOULD_RUN_BUILD_EXE)("build-exe-cross: single target", () => {
|
||||
|
||||
describe.skipIf(!SHOULD_RUN_BUILD_EXE)("build-exe-cross: windows target has .exe extension", () => {
|
||||
beforeAll(() => {
|
||||
const bin = join(distDir, "fn-windows-x64.exe");
|
||||
const bin = join(distDir, "fn-cli-windows-x64.exe");
|
||||
if (existsSync(bin)) return;
|
||||
execSync("bun run build.ts --target bun-windows-x64", {
|
||||
cwd: cliRoot,
|
||||
@@ -82,8 +82,8 @@ describe.skipIf(!SHOULD_RUN_BUILD_EXE)("build-exe-cross: windows target has .exe
|
||||
});
|
||||
}, 180_000);
|
||||
|
||||
it("produces dist/fn-windows-x64.exe", () => {
|
||||
const bin = join(distDir, "fn-windows-x64.exe");
|
||||
it("produces dist/fn-cli-windows-x64.exe", () => {
|
||||
const bin = join(distDir, "fn-cli-windows-x64.exe");
|
||||
expect(existsSync(bin)).toBe(true);
|
||||
expect(statSync(bin).size).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -485,10 +485,10 @@ describe("Binary release workflow (.github/workflows/release.yml)", () => {
|
||||
expect(osMap["bun-windows-x64"]).toBe("windows-latest");
|
||||
});
|
||||
|
||||
it("maps bun-linux-arm64 to fn-linux-arm64 binary name", () => {
|
||||
it("maps bun-linux-arm64 to fn-cli-linux-arm64 binary name", () => {
|
||||
const matrix = workflow.jobs["build-binaries"].strategy.matrix.include;
|
||||
const arm64Entry = matrix.find((m: any) => m.target === "bun-linux-arm64");
|
||||
expect(arm64Entry?.binary).toBe("fn-linux-arm64");
|
||||
expect(arm64Entry?.binary).toBe("fn-cli-linux-arm64");
|
||||
});
|
||||
|
||||
it("uses softprops/action-gh-release", () => {
|
||||
@@ -586,10 +586,10 @@ describe("Test-release workflow (.github/workflows/test-release.yml)", () => {
|
||||
expect(targets).not.toContain("bun-darwin-x64");
|
||||
});
|
||||
|
||||
it("maps bun-linux-arm64 to fn-linux-arm64 binary name", () => {
|
||||
it("maps bun-linux-arm64 to fn-cli-linux-arm64 binary name", () => {
|
||||
const matrix = workflow.jobs["build-binaries"].strategy.matrix.include;
|
||||
const arm64Entry = matrix.find((m: any) => m.target === "bun-linux-arm64");
|
||||
expect(arm64Entry?.binary).toBe("fn-linux-arm64");
|
||||
expect(arm64Entry?.binary).toBe("fn-cli-linux-arm64");
|
||||
});
|
||||
|
||||
it("includes smoke tests with --help", () => {
|
||||
|
||||
@@ -96,11 +96,11 @@ describe("CLI package.json publishing config", () => {
|
||||
it("does not include bare 'dist' entry or globs that would match Bun binaries", () => {
|
||||
const bunBinaryNames = [
|
||||
"fn",
|
||||
"fn-linux-x64",
|
||||
"fn-linux-arm64",
|
||||
"fn-darwin-x64",
|
||||
"fn-darwin-arm64",
|
||||
"fn-windows-x64.exe",
|
||||
"fn-cli-linux-x64",
|
||||
"fn-cli-linux-arm64",
|
||||
"fn-cli-darwin-x64",
|
||||
"fn-cli-darwin-arm64",
|
||||
"fn-cli-windows-x64.exe",
|
||||
];
|
||||
// No bare "dist" entry that would include everything
|
||||
expect(pkg.files).not.toContain("dist");
|
||||
|
||||
@@ -585,4 +585,152 @@ describe("wrapAuthStorageWithApiKeyProviders", () => {
|
||||
expect(merged.has("anthropic")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("anthropic-subscription getApiKey delegation (FN-7576)", () => {
|
||||
/*
|
||||
FNXC:ProviderAuth 2026-07-05-09:15:
|
||||
These tests drive `wrapAuthStorageWithApiKeyProviders`/`mergeAuthStorageReads` directly against an instrumented fake engine `authStorage.getApiKey`, not a mocked dashboard/engine `AuthStorageLike`, per FN-7576's "fix the invariant, not the repro" requirement. They assert the wrapper actually DELEGATES the anthropic-subscription read to the real engine authStorage (so the refresh HTTP round trip in packages/engine/src/auth-storage.ts executes) rather than short-circuiting to a local static `Date.now() >= credential.expires` check.
|
||||
*/
|
||||
const PAST_EXPIRY = Date.now() - 60_000;
|
||||
|
||||
it("delegates to the underlying engine authStorage.getApiKey and returns the refreshed key even when the stored credential is expired", async () => {
|
||||
const fusionAuth = makeAuthStorage({
|
||||
"anthropic-subscription": {
|
||||
type: "oauth",
|
||||
access: "stale-oauth-access",
|
||||
refresh: "stale-oauth-refresh",
|
||||
expires: PAST_EXPIRY,
|
||||
},
|
||||
});
|
||||
fusionAuth.getApiKey = vi.fn(async (provider: string) =>
|
||||
provider === "anthropic-subscription" ? "refreshed-oauth-access" : undefined,
|
||||
);
|
||||
const modelRegistry = { getAll: vi.fn(() => []) } as any;
|
||||
|
||||
const wrapped = wrapAuthStorageWithApiKeyProviders(fusionAuth, modelRegistry);
|
||||
const apiKey = await wrapped.getApiKey("anthropic-subscription");
|
||||
|
||||
expect(fusionAuth.getApiKey).toHaveBeenCalledWith("anthropic-subscription");
|
||||
expect(apiKey).toBe("refreshed-oauth-access");
|
||||
});
|
||||
|
||||
it("delegates via the mergeAuthStorageReads proxy directly", async () => {
|
||||
const fusionAuth = makeAuthStorage({
|
||||
"anthropic-subscription": {
|
||||
type: "oauth",
|
||||
access: "stale-oauth-access",
|
||||
refresh: "stale-oauth-refresh",
|
||||
expires: PAST_EXPIRY,
|
||||
},
|
||||
});
|
||||
fusionAuth.getApiKey = vi.fn(async (provider: string) =>
|
||||
provider === "anthropic-subscription" ? "refreshed-oauth-access" : undefined,
|
||||
);
|
||||
|
||||
const merged = mergeAuthStorageReads(fusionAuth);
|
||||
const apiKey = await merged.getApiKey("anthropic-subscription");
|
||||
|
||||
expect(fusionAuth.getApiKey).toHaveBeenCalledWith("anthropic-subscription");
|
||||
expect(apiKey).toBe("refreshed-oauth-access");
|
||||
});
|
||||
|
||||
it("delegates and returns the refreshed key when OAuth is stored under the legacy anthropic row", async () => {
|
||||
const fusionAuth = makeAuthStorage({
|
||||
anthropic: {
|
||||
type: "oauth",
|
||||
access: "legacy-stale-oauth-access",
|
||||
refresh: "legacy-stale-oauth-refresh",
|
||||
expires: PAST_EXPIRY,
|
||||
},
|
||||
});
|
||||
fusionAuth.getApiKey = vi.fn(async (provider: string) =>
|
||||
provider === "anthropic-subscription" ? "refreshed-oauth-access" : undefined,
|
||||
);
|
||||
const modelRegistry = { getAll: vi.fn(() => []) } as any;
|
||||
|
||||
const wrapped = wrapAuthStorageWithApiKeyProviders(fusionAuth, modelRegistry);
|
||||
const apiKey = await wrapped.getApiKey("anthropic-subscription");
|
||||
|
||||
expect(fusionAuth.getApiKey).toHaveBeenCalledWith("anthropic-subscription");
|
||||
expect(apiKey).toBe("refreshed-oauth-access");
|
||||
});
|
||||
|
||||
it("does not call the delegated engine getApiKey and returns undefined once the subscription is logged out", async () => {
|
||||
const fusionAuth = makeAuthStorage({
|
||||
"anthropic-subscription": {
|
||||
type: "oauth",
|
||||
access: "oauth-access",
|
||||
refresh: "oauth-refresh",
|
||||
expires: Date.now() + 60_000,
|
||||
},
|
||||
});
|
||||
fusionAuth.getApiKey = vi.fn(async (provider: string) =>
|
||||
provider === "anthropic-subscription" ? "refreshed-oauth-access" : undefined,
|
||||
);
|
||||
const modelRegistry = { getAll: vi.fn(() => []) } as any;
|
||||
|
||||
const wrapped = wrapAuthStorageWithApiKeyProviders(fusionAuth, modelRegistry);
|
||||
wrapped.logout("anthropic-subscription");
|
||||
fusionAuth.getApiKey.mockClear();
|
||||
|
||||
const apiKey = await wrapped.getApiKey("anthropic-subscription");
|
||||
|
||||
expect(apiKey).toBeUndefined();
|
||||
expect(fusionAuth.getApiKey).not.toHaveBeenCalledWith("anthropic-subscription");
|
||||
});
|
||||
|
||||
it("returns undefined when no credential is stored and the engine authStorage has nothing to refresh", async () => {
|
||||
const fusionAuth = makeAuthStorage();
|
||||
const modelRegistry = { getAll: vi.fn(() => []) } as any;
|
||||
|
||||
const wrapped = wrapAuthStorageWithApiKeyProviders(fusionAuth, modelRegistry);
|
||||
const apiKey = await wrapped.getApiKey("anthropic-subscription");
|
||||
|
||||
expect(apiKey).toBeUndefined();
|
||||
});
|
||||
|
||||
it("falls back to a read-only fallback storage's local resolution only when the primary engine yields no key", async () => {
|
||||
const fusionAuth = makeAuthStorage({
|
||||
"anthropic-subscription": {
|
||||
type: "oauth",
|
||||
access: "stale-oauth-access",
|
||||
refresh: "stale-oauth-refresh",
|
||||
expires: PAST_EXPIRY,
|
||||
},
|
||||
});
|
||||
// Primary engine authStorage genuinely cannot refresh (e.g. refresh token invalid/absent upstream).
|
||||
fusionAuth.getApiKey = vi.fn(async () => undefined);
|
||||
const fallbackAuth = makeAuthStorage({
|
||||
"anthropic-subscription": {
|
||||
type: "oauth",
|
||||
access: "fallback-still-valid-access",
|
||||
refresh: "fallback-refresh",
|
||||
expires: Date.now() + 60_000,
|
||||
},
|
||||
});
|
||||
fallbackAuth.getApiKey = vi.fn(async (provider: string) =>
|
||||
provider === "anthropic-subscription" ? "fallback-still-valid-access" : undefined,
|
||||
);
|
||||
const modelRegistry = { getAll: vi.fn(() => []) } as any;
|
||||
|
||||
const wrapped = wrapAuthStorageWithApiKeyProviders(fusionAuth, modelRegistry, [fallbackAuth]);
|
||||
const apiKey = await wrapped.getApiKey("anthropic-subscription");
|
||||
|
||||
expect(fusionAuth.getApiKey).toHaveBeenCalledWith("anthropic-subscription");
|
||||
expect(apiKey).toBe("fallback-still-valid-access");
|
||||
});
|
||||
|
||||
it("still resolves non-subscription providers exactly as before (no regression)", async () => {
|
||||
const fusionAuth = makeAuthStorage({
|
||||
openrouter: { type: "api_key", key: "fusion-openrouter-key" },
|
||||
anthropic: { type: "api_key", key: "sk-ant-api03-raw-key" },
|
||||
});
|
||||
const modelRegistry = { getAll: vi.fn(() => []) } as any;
|
||||
|
||||
const wrapped = wrapAuthStorageWithApiKeyProviders(fusionAuth, modelRegistry);
|
||||
|
||||
expect(await wrapped.getApiKey("openrouter")).toBe("fusion-openrouter-key");
|
||||
expect(await wrapped.getApiKey("anthropic-api-key")).toBe("sk-ant-api03-raw-key");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -415,7 +415,17 @@ export function mergeAuthStorageReads(
|
||||
return credential?.type === "api_key" ? resolveStoredApiKey(credential.key) : undefined;
|
||||
}
|
||||
if (providerId === ANTHROPIC_SUBSCRIPTION_STORAGE_PROVIDER_ID && credential) {
|
||||
return resolveStoredCredentialApiKey(providerId, credential);
|
||||
/*
|
||||
FNXC:ProviderAuth 2026-07-05-09:10:
|
||||
Reading `anthropic-subscription` through this merge proxy must delegate to the underlying real engine `authStorage.getApiKey(...)` (the `target` primary storage) so the refresh-token HTTP round trip in packages/engine/src/auth-storage.ts actually runs. The prior local static `Date.now() >= credential.expires` check (`resolveStoredCredentialApiKey`/`resolveOAuthApiKey`) never called the real engine and silently no-oped the refresh in production, e.g. the dashboard status route's best-effort refresh-on-expiry read (register-auth-routes.ts). `target.getApiKey` internally handles both the separated `anthropic-subscription` row and the legacy `anthropic` OAuth row, so this single delegated call covers both storage permutations without duplicating that logic here. Only fall back to the read-only fallback storages' local (non-refreshing) resolution when the primary engine yields no key; a logged-out subscription is already excluded above and must never reach this delegated call.
|
||||
*/
|
||||
const engineApiKey = await target.getApiKey(providerId);
|
||||
if (engineApiKey) return engineApiKey;
|
||||
for (const fallbackStorage of readFallbackAuthStorages) {
|
||||
const fallbackApiKey = await fallbackStorage.getApiKey(providerId);
|
||||
if (fallbackApiKey) return fallbackApiKey;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
for (const storage of readAuthStorages) {
|
||||
const apiKey = await storage.getApiKey(providerId);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# @fusion/core
|
||||
|
||||
## 0.56.1
|
||||
|
||||
## 0.56.0
|
||||
|
||||
## 0.55.0
|
||||
|
||||
## 0.54.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@fusion/core",
|
||||
"version": "0.55.0",
|
||||
"version": "0.56.1",
|
||||
"license": "MIT",
|
||||
"description": "Fusion core: task store, scheduler, settings, and shared domain types backing the Fusion AI coding agent.",
|
||||
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
||||
|
||||
@@ -312,6 +312,26 @@ describe("resolveAgentPrompt", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("places the Before → After Transformation section at the top of the definition, ahead of Mission and Review Level (FN-7593)", () => {
|
||||
const standardPrompt = resolveAgentPrompt("triage");
|
||||
const fastPrompt = builtinSeamPrompt("planning-fast");
|
||||
|
||||
const standardTransformationIdx = standardPrompt.indexOf("## Before → After Transformation");
|
||||
const standardReviewLevelIdx = standardPrompt.indexOf("## Review Level");
|
||||
const standardMissionIdx = standardPrompt.indexOf("## Mission");
|
||||
expect(standardTransformationIdx).toBeGreaterThan(-1);
|
||||
expect(standardReviewLevelIdx).toBeGreaterThan(-1);
|
||||
expect(standardMissionIdx).toBeGreaterThan(-1);
|
||||
expect(standardTransformationIdx).toBeLessThan(standardReviewLevelIdx);
|
||||
expect(standardTransformationIdx).toBeLessThan(standardMissionIdx);
|
||||
|
||||
const fastTransformationIdx = fastPrompt.indexOf("## Before → After Transformation");
|
||||
const fastMissionIdx = fastPrompt.indexOf("## Mission");
|
||||
expect(fastTransformationIdx).toBeGreaterThan(-1);
|
||||
expect(fastMissionIdx).toBeGreaterThan(-1);
|
||||
expect(fastTransformationIdx).toBeLessThan(fastMissionIdx);
|
||||
});
|
||||
|
||||
it("triage planning prompt is sourced from workflow IR without an engine duplicate", () => {
|
||||
const corePrompt = resolveAgentPrompt("triage");
|
||||
const planningPrompt = resolvePlanningPromptFromIr(BUILTIN_CODING_WORKFLOW_IR);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { BUILTIN_BRAINSTORMING_WORKFLOW_IR } from "../builtin-brainstorming-workflow-ir.js";
|
||||
import { parseWorkflowIr, serializeWorkflowIr } from "../workflow-ir.js";
|
||||
import type { WorkflowIrV2 } from "../workflow-ir-types.js";
|
||||
|
||||
/*
|
||||
FNXC:WorkflowBrainstorming 2026-07-05-00:00:
|
||||
FN-7584 registers builtin:brainstorming as a discoverable built-in composing
|
||||
FN-7579's ask-user -> refine -> exit-gate-on-approval phase ahead of the normal
|
||||
coding plan/execute path. These tests pin the IR shape: it parses, orders the
|
||||
brainstorm nodes before the plan/execute spine, and round-trips through
|
||||
serialize/parse.
|
||||
*/
|
||||
|
||||
describe("builtin brainstorming workflow IR", () => {
|
||||
it("parses through parseWorkflowIr", () => {
|
||||
expect(() => parseWorkflowIr(BUILTIN_BRAINSTORMING_WORKFLOW_IR)).not.toThrow();
|
||||
const ir = BUILTIN_BRAINSTORMING_WORKFLOW_IR as WorkflowIrV2;
|
||||
expect(ir.version).toBe("v2");
|
||||
});
|
||||
|
||||
it("contains an ask-user and an exit-gate node ordered before the plan/execute spine", () => {
|
||||
const ir = BUILTIN_BRAINSTORMING_WORKFLOW_IR as WorkflowIrV2;
|
||||
|
||||
const askUser = ir.nodes.find((node) => node.kind === "ask-user");
|
||||
const exitGate = ir.nodes.find((node) => node.kind === "exit-gate");
|
||||
expect(askUser?.id).toBe("brainstorm-ask");
|
||||
expect(exitGate?.id).toBe("brainstorm-exit");
|
||||
|
||||
const startIndex = ir.nodes.findIndex((node) => node.id === "start");
|
||||
const askIndex = ir.nodes.findIndex((node) => node.id === "brainstorm-ask");
|
||||
const refineIndex = ir.nodes.findIndex((node) => node.id === "brainstorm-refine");
|
||||
const exitIndex = ir.nodes.findIndex((node) => node.id === "brainstorm-exit");
|
||||
const planIndex = ir.nodes.findIndex((node) => node.id === "plan");
|
||||
const parseIndex = ir.nodes.findIndex((node) => node.id === "parse");
|
||||
const stepsIndex = ir.nodes.findIndex((node) => node.id === "steps");
|
||||
|
||||
expect(startIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(askIndex).toBeGreaterThan(startIndex);
|
||||
expect(refineIndex).toBeGreaterThan(askIndex);
|
||||
expect(exitIndex).toBeGreaterThan(refineIndex);
|
||||
expect(planIndex).toBeGreaterThan(exitIndex);
|
||||
expect(parseIndex).toBeGreaterThan(planIndex);
|
||||
expect(stepsIndex).toBeGreaterThan(parseIndex);
|
||||
|
||||
// Graph wiring: start feeds the brainstorm loop, and the exit-gate's
|
||||
// outcome:exit edge is what rejoins the unmodified plan/execute spine.
|
||||
expect(ir.edges).toContainEqual({ from: "start", to: "brainstorm-ask", condition: "success" });
|
||||
expect(ir.edges).toContainEqual({ from: "brainstorm-ask", to: "brainstorm-refine", condition: "success" });
|
||||
expect(ir.edges).toContainEqual({ from: "brainstorm-refine", to: "brainstorm-exit", condition: "success" });
|
||||
expect(ir.edges).toContainEqual({ from: "brainstorm-exit", to: "plan", condition: "outcome:exit" });
|
||||
expect(ir.edges).toContainEqual({
|
||||
from: "brainstorm-exit",
|
||||
to: "brainstorm-ask",
|
||||
condition: "outcome:continue",
|
||||
kind: "rework",
|
||||
});
|
||||
|
||||
// The exit-gate's rework edge targets a declared top-level rework-region head.
|
||||
expect(askUser?.config?.reworkRegion).toBe(true);
|
||||
expect(typeof askUser?.config?.maxReworkCycles).toBe("number");
|
||||
|
||||
// ask-user carries a non-empty question (validator-enforced when present).
|
||||
expect(typeof askUser?.config?.question).toBe("string");
|
||||
expect((askUser?.config?.question as string).trim().length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("round-trips serialize -> parse to identical bytes", () => {
|
||||
const serialized = serializeWorkflowIr(BUILTIN_BRAINSTORMING_WORKFLOW_IR);
|
||||
const reparsed = parseWorkflowIr(serialized);
|
||||
expect(serializeWorkflowIr(reparsed)).toBe(serialized);
|
||||
});
|
||||
|
||||
it("carries the shared built-in workflow settings", () => {
|
||||
const ir = BUILTIN_BRAINSTORMING_WORKFLOW_IR as WorkflowIrV2;
|
||||
expect(ir.settings?.some((setting) => setting.id === "planReviewMaxRevisions")).toBe(true);
|
||||
expect(ir.settings?.some((setting) => setting.id === "codeReviewMaxRevisions")).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -107,6 +107,13 @@ describe("builtin coding workflow ir", () => {
|
||||
expect(ids).toEqual(["triage", "todo", "in-progress", "in-review", "done", "archived"]);
|
||||
});
|
||||
|
||||
// FNXC:Workflows 2026-07-05-00:00: FN-7599 — the intake column displays as "Planning" while its id stays "triage" for lifecycle/DB/type stability.
|
||||
it("labels the intake column 'Planning' while keeping the 'triage' id (FN-7599)", () => {
|
||||
if (BUILTIN_CODING_WORKFLOW_IR.version !== "v2") throw new Error("expected v2");
|
||||
const byId = new Map(BUILTIN_CODING_WORKFLOW_IR.columns.map((c) => [c.id, c]));
|
||||
expect(byId.get("triage")).toEqual({ id: "triage", name: "Planning", traits: [{ trait: "intake" }] });
|
||||
});
|
||||
|
||||
it("maps default-workflow traits to columns verbatim (R12)", () => {
|
||||
if (BUILTIN_CODING_WORKFLOW_IR.version !== "v2") throw new Error("expected v2");
|
||||
const byId = new Map(BUILTIN_CODING_WORKFLOW_IR.columns.map((c) => [c.id, c]));
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "../builtin-workflows.js";
|
||||
import { BUILTIN_CODING_WORKFLOW_IR } from "../builtin-coding-workflow-ir.js";
|
||||
import { BUILTIN_STEPWISE_CODING_WORKFLOW_IR } from "../builtin-stepwise-coding-workflow-ir.js";
|
||||
import { BUILTIN_PR_WORKFLOW_IR } from "../builtin-pr-workflow-ir.js";
|
||||
import { BROWSER_VERIFICATION_GROUP_ID, BROWSER_VERIFICATION_STEP_NODE_ID } from "../builtin-browser-verification-group.js";
|
||||
import { CODE_REVIEW_STEP_NODE_ID } from "../builtin-code-review-group.js";
|
||||
import { PLAN_REVIEW_GROUP_ID, PLAN_REVIEW_STEP_NODE_ID } from "../builtin-plan-review-group.js";
|
||||
@@ -343,6 +344,72 @@ describe("built-in workflows", () => {
|
||||
);
|
||||
});
|
||||
|
||||
/*
|
||||
* FNXC:WorkflowBrainstorming 2026-07-05-00:00:
|
||||
* FN-7584 parity coverage for the registered builtin:brainstorming built-in
|
||||
* (FN-7579's ask-user -> refine -> exit-gate-on-approval composition,
|
||||
* discoverable from the workflow picker ahead of the normal coding spine).
|
||||
*/
|
||||
it("registers builtin:brainstorming as a default-enabled workflow ordered after the existing built-ins", () => {
|
||||
const brainstorming = getBuiltinWorkflow("builtin:brainstorming");
|
||||
expect(brainstorming).toBeDefined();
|
||||
expect(brainstorming!.kind).toBe("workflow");
|
||||
expect(() => parseWorkflowIr(brainstorming!.ir)).not.toThrow();
|
||||
expect(defaultEnabledBuiltinWorkflowIds()).toContain("builtin:brainstorming");
|
||||
expect(BUILTIN_WORKFLOWS.findIndex((workflow) => workflow.id === "builtin:brainstorming")).toBeGreaterThan(
|
||||
BUILTIN_WORKFLOWS.findIndex((workflow) => workflow.id === "builtin:lead-generation"),
|
||||
);
|
||||
// Ordering assertion the suite pins elsewhere (`.slice(0, 5)`) must stay untouched.
|
||||
expect(defaultEnabledBuiltinWorkflowIds().slice(0, 5)).toEqual([
|
||||
"builtin:coding",
|
||||
"builtin:coding-ideas",
|
||||
"builtin:legacy-coding",
|
||||
"builtin:quick-fix",
|
||||
"builtin:review-heavy",
|
||||
]);
|
||||
});
|
||||
|
||||
it("orders builtin:brainstorming's ask-user/exit-gate loop ahead of the plan/execute spine", () => {
|
||||
const brainstorming = getBuiltinWorkflow("builtin:brainstorming")!;
|
||||
const nodes = brainstorming.ir.nodes;
|
||||
const askIndex = nodes.findIndex((node) => node.kind === "ask-user");
|
||||
const exitIndex = nodes.findIndex((node) => node.kind === "exit-gate");
|
||||
const planIndex = nodes.findIndex((node) => node.id === "plan");
|
||||
const parseIndex = nodes.findIndex((node) => node.id === "parse");
|
||||
const stepsIndex = nodes.findIndex((node) => node.id === "steps");
|
||||
|
||||
expect(askIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(exitIndex).toBeGreaterThan(askIndex);
|
||||
expect(planIndex).toBeGreaterThan(exitIndex);
|
||||
expect(parseIndex).toBeGreaterThan(planIndex);
|
||||
expect(stepsIndex).toBeGreaterThan(parseIndex);
|
||||
});
|
||||
|
||||
it("builtin:brainstorming still satisfies every merge-capable built-in invariant (completion summary, post-merge-verification, merge primitives, settings)", () => {
|
||||
const brainstorming = getBuiltinWorkflow("builtin:brainstorming")!;
|
||||
const summaryNodes = brainstorming.ir.nodes.filter(
|
||||
(node) => node.kind === "prompt" && (node.config as { summaryTarget?: unknown } | undefined)?.summaryTarget === "task",
|
||||
);
|
||||
expect(summaryNodes).toHaveLength(1);
|
||||
expect((summaryNodes[0]!.config as { toolMode?: unknown }).toolMode).toBe("readonly");
|
||||
|
||||
expect(brainstorming.ir.nodes.some((node) => node.id === "post-merge-verification")).toBe(true);
|
||||
for (const id of [
|
||||
"merge-gate",
|
||||
"merge-retry",
|
||||
"merge-manual-hold",
|
||||
"branch-group-member-integration",
|
||||
"branch-group-promotion",
|
||||
"merge-attempt",
|
||||
"recovery-router",
|
||||
]) {
|
||||
expect(brainstorming.ir.nodes.some((node) => node.id === id)).toBe(true);
|
||||
}
|
||||
|
||||
expect(brainstorming.ir.settings?.some((setting) => setting.id === "planReviewMaxRevisions")).toBe(true);
|
||||
expect(brainstorming.ir.settings?.some((setting) => setting.id === "codeReviewMaxRevisions")).toBe(true);
|
||||
});
|
||||
|
||||
it("default workflow column ids equal the legacy enum values, in legacy order (KTD-1)", () => {
|
||||
expect(BUILTIN_CODING_WORKFLOW_IR.version).toBe("v2");
|
||||
if (BUILTIN_CODING_WORKFLOW_IR.version !== "v2") throw new Error("expected v2");
|
||||
@@ -351,6 +418,17 @@ describe("built-in workflows", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
// FNXC:Workflows 2026-07-05-00:00: FN-7599 — hand-authored default workflows (stepwise-coding, pr-workflow)
|
||||
// must also label the intake column "Planning" while keeping the "triage" id, matching builtin-coding.
|
||||
it("hand-authored default workflows label the intake column 'Planning' (FN-7599)", () => {
|
||||
for (const ir of [BUILTIN_STEPWISE_CODING_WORKFLOW_IR, BUILTIN_PR_WORKFLOW_IR]) {
|
||||
expect(ir.version).toBe("v2");
|
||||
if (ir.version !== "v2") throw new Error("expected v2");
|
||||
const triageColumn = ir.columns.find((column) => column.id === "triage");
|
||||
expect(triageColumn).toEqual({ id: "triage", name: "Planning", traits: [{ trait: "intake" }] });
|
||||
}
|
||||
});
|
||||
|
||||
it("builtin:coding catalog entry is backed by the stepwise final-review IR", () => {
|
||||
const coding = getBuiltinWorkflow("builtin:coding");
|
||||
expect(coding).toBeDefined();
|
||||
|
||||
59
packages/core/src/__tests__/coding-ideas-move.test.ts
Normal file
59
packages/core/src/__tests__/coding-ideas-move.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
/*
|
||||
FNXC:WorkflowColumns 2026-07-05-19:10:
|
||||
Regression for the disappearing move on custom workflow columns. Workflow columns graduated to
|
||||
always-on (no experimental flag emitted), but moveTaskInternal still gated its workflow path on the
|
||||
retired strict compatibility flag, so it fell back to the legacy VALID_TRANSITIONS table — which is
|
||||
keyed only by the legacy column ids. A task in a non-legacy column (Coding (Ideas) → "ideas") could
|
||||
not move: "Invalid transition: 'ideas' → 'todo'. Valid targets: none".
|
||||
|
||||
Surface enumeration (invariant: a move is validated by the task's WORKFLOW adjacency, not the legacy
|
||||
table, on a default project with NO experimental flag set):
|
||||
- Custom intake column forward move: ideas → todo is allowed.
|
||||
- Full custom-column chain onward: todo → in-progress → in-review all succeed (→ done is gated by the
|
||||
workflow's own merge trait, which is orthogonal to transition adjacency and excluded here).
|
||||
- Non-adjacent move still rejects with the workflow's targets (ideas → in-progress).
|
||||
- Holds for both user- and engine-sourced moves.
|
||||
- Default workflow (legacy column ids) is unchanged (parity), verified in move-task-characterization.
|
||||
*/
|
||||
describe("Coding (Ideas) custom-column moves (workflow-columns graduation)", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
beforeEach(harness.beforeEach);
|
||||
afterEach(harness.afterEach);
|
||||
|
||||
it("moves an ideas-workflow task from the ideas intake column to todo", async () => {
|
||||
const store = harness.store();
|
||||
const task = await store.createTask({ description: "idea", workflowId: "builtin:coding-ideas" });
|
||||
expect(task.column).toBe("ideas");
|
||||
|
||||
const moved = await store.moveTask(task.id, "todo", { moveSource: "user" });
|
||||
expect(moved.column).toBe("todo");
|
||||
});
|
||||
|
||||
it("advances an ideas task along the Coding (Ideas) custom-column chain", async () => {
|
||||
const store = harness.store();
|
||||
const task = await store.createTask({ description: "idea", workflowId: "builtin:coding-ideas" });
|
||||
|
||||
await store.moveTask(task.id, "todo", { moveSource: "user" });
|
||||
await store.moveTask(task.id, "in-progress", { moveSource: "user" });
|
||||
const inReview = await store.moveTask(task.id, "in-review", { moveSource: "user", allowDirectInReviewMove: true });
|
||||
expect(inReview.column).toBe("in-review");
|
||||
});
|
||||
|
||||
it("still rejects a non-adjacent move out of the ideas column", async () => {
|
||||
const store = harness.store();
|
||||
const task = await store.createTask({ description: "idea", workflowId: "builtin:coding-ideas" });
|
||||
await expect(
|
||||
store.moveTask(task.id, "in-progress", { moveSource: "user" }),
|
||||
).rejects.toThrow(/Invalid transition: 'ideas' → 'in-progress'/);
|
||||
});
|
||||
|
||||
it("allows the ideas → todo move from an engine source too", async () => {
|
||||
const store = harness.store();
|
||||
const task = await store.createTask({ description: "idea", workflowId: "builtin:coding-ideas" });
|
||||
const moved = await store.moveTask(task.id, "todo", { moveSource: "engine" });
|
||||
expect(moved.column).toBe("todo");
|
||||
});
|
||||
});
|
||||
@@ -1823,6 +1823,113 @@ describe("schema migrations", () => {
|
||||
db.close();
|
||||
});
|
||||
|
||||
/*
|
||||
* FNXC:PlanApproval 2026-07-04-21:35:
|
||||
* FN-7559: migration 138 adds the awaitingApprovalReason discriminator so
|
||||
* a release-authorization hold (status "awaiting-approval") can be told apart
|
||||
* from a manual plan-approval hold sharing the identical status. Additive-only,
|
||||
* no backfill — legacy rows stay NULL, meaning "no reason recorded" (either no
|
||||
* hold, or an ordinary manual hold).
|
||||
*/
|
||||
it("migrates v137 databases by adding awaitingApprovalReason column with legacy rows staying NULL (no backfill)", () => {
|
||||
tmpDir = makeTmpDir();
|
||||
const fusionDir = join(tmpDir, ".fusion");
|
||||
const db = new Database(fusionDir);
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS __meta (key TEXT PRIMARY KEY, value TEXT);
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id TEXT PRIMARY KEY,
|
||||
description TEXT NOT NULL,
|
||||
"column" TEXT NOT NULL,
|
||||
status TEXT,
|
||||
createdAt TEXT NOT NULL,
|
||||
updatedAt TEXT NOT NULL,
|
||||
executionMode TEXT DEFAULT 'standard',
|
||||
plannerOversightLevel TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
nextId INTEGER DEFAULT 1,
|
||||
nextWorkflowStepId INTEGER DEFAULT 1,
|
||||
settings TEXT DEFAULT '{}',
|
||||
workflowSteps TEXT DEFAULT '[]',
|
||||
updatedAt TEXT
|
||||
);
|
||||
`);
|
||||
db.exec("INSERT INTO __meta (key, value) VALUES ('schemaVersion', '137')");
|
||||
db.exec("INSERT INTO __meta (key, value) VALUES ('lastModified', '1000')");
|
||||
db.exec(`INSERT INTO tasks (id, description, "column", status, createdAt, updatedAt) VALUES ('FN-1', 'legacy', 'triage', 'awaiting-approval', '2026-01-01', '2026-01-01')`);
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(SCHEMA_VERSION);
|
||||
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
expect(cols.map((col) => col.name)).toContain("awaitingApprovalReason");
|
||||
|
||||
const task = db.prepare("SELECT awaitingApprovalReason FROM tasks WHERE id = 'FN-1'").get() as {
|
||||
awaitingApprovalReason: string | null;
|
||||
};
|
||||
expect(task.awaitingApprovalReason).toBeNull();
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
/*
|
||||
* FNXC:PlanApproval 2026-07-04-22:41:
|
||||
* FN-7569: migration 139 adds approvedPlanFingerprint so manual plan approval can be
|
||||
* idempotent against unchanged plan content — an approved-then-re-specified task whose
|
||||
* PROMPT.md is unchanged should not be re-parked at awaiting-approval. Additive-only,
|
||||
* no backfill — legacy rows stay NULL, meaning "never approved" (falls back to today's
|
||||
* always-re-park behavior).
|
||||
*/
|
||||
it("migrates v138 databases by adding approvedPlanFingerprint column with legacy rows staying NULL (no backfill)", () => {
|
||||
tmpDir = makeTmpDir();
|
||||
const fusionDir = join(tmpDir, ".fusion");
|
||||
const db = new Database(fusionDir);
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS __meta (key TEXT PRIMARY KEY, value TEXT);
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id TEXT PRIMARY KEY,
|
||||
description TEXT NOT NULL,
|
||||
"column" TEXT NOT NULL,
|
||||
status TEXT,
|
||||
createdAt TEXT NOT NULL,
|
||||
updatedAt TEXT NOT NULL,
|
||||
executionMode TEXT DEFAULT 'standard',
|
||||
plannerOversightLevel TEXT,
|
||||
awaitingApprovalReason TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
nextId INTEGER DEFAULT 1,
|
||||
nextWorkflowStepId INTEGER DEFAULT 1,
|
||||
settings TEXT DEFAULT '{}',
|
||||
workflowSteps TEXT DEFAULT '[]',
|
||||
updatedAt TEXT
|
||||
);
|
||||
`);
|
||||
db.exec("INSERT INTO __meta (key, value) VALUES ('schemaVersion', '138')");
|
||||
db.exec("INSERT INTO __meta (key, value) VALUES ('lastModified', '1000')");
|
||||
db.exec(`INSERT INTO tasks (id, description, "column", status, createdAt, updatedAt) VALUES ('FN-1', 'legacy', 'triage', 'awaiting-approval', '2026-01-01', '2026-01-01')`);
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(SCHEMA_VERSION);
|
||||
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
expect(cols.map((col) => col.name)).toContain("approvedPlanFingerprint");
|
||||
|
||||
const task = db.prepare("SELECT approvedPlanFingerprint FROM tasks WHERE id = 'FN-1'").get() as {
|
||||
approvedPlanFingerprint: string | null;
|
||||
};
|
||||
expect(task.approvedPlanFingerprint).toBeNull();
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
it("migrates v43 databases by adding task token-usage aggregate columns with null-compatible defaults", () => {
|
||||
tmpDir = makeTmpDir();
|
||||
const fusionDir = join(tmpDir, ".fusion");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolvePlanApprovalRequired, type PlanApprovalMode } from "../plan-approval.js";
|
||||
import { computePlanApprovalFingerprint, resolvePlanApprovalRequired, type PlanApprovalMode } from "../plan-approval.js";
|
||||
|
||||
const workflowValues = [true, false, undefined] as const;
|
||||
|
||||
@@ -35,3 +35,32 @@ describe("resolvePlanApprovalRequired", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* FNXC:PlanApproval 2026-07-04-22:41:
|
||||
* FN-7569 — computePlanApprovalFingerprint coverage: stable for identical content, normalizes only
|
||||
* trailing whitespace/newlines, and differs whenever the actual plan body changes.
|
||||
*/
|
||||
describe("computePlanApprovalFingerprint", () => {
|
||||
it("is stable for the same content across repeated calls", () => {
|
||||
const text = "# Task: FN-1\n\n## File Scope\n\n- a.ts\n";
|
||||
expect(computePlanApprovalFingerprint(text)).toBe(computePlanApprovalFingerprint(text));
|
||||
});
|
||||
|
||||
it("is unaffected by trailing whitespace or trailing newline differences", () => {
|
||||
const base = "# Task: FN-1\n\n## File Scope\n\n- a.ts";
|
||||
expect(computePlanApprovalFingerprint(base)).toBe(computePlanApprovalFingerprint(`${base}\n`));
|
||||
expect(computePlanApprovalFingerprint(base)).toBe(computePlanApprovalFingerprint(`${base}\n\n\n`));
|
||||
expect(computePlanApprovalFingerprint("line one \nline two")).toBe(computePlanApprovalFingerprint("line one\nline two"));
|
||||
});
|
||||
|
||||
it("differs when the plan content actually changes", () => {
|
||||
const original = "# Task: FN-1\n\n## File Scope\n\n- a.ts\n";
|
||||
const changed = "# Task: FN-1\n\n## File Scope\n\n- a.ts\n- b.ts\n";
|
||||
expect(computePlanApprovalFingerprint(original)).not.toBe(computePlanApprovalFingerprint(changed));
|
||||
});
|
||||
|
||||
it("produces a hex sha256-length digest", () => {
|
||||
expect(computePlanApprovalFingerprint("anything")).toMatch(/^[0-9a-f]{64}$/);
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user