Commit Graph

11415 Commits

Author SHA1 Message Date
gsxdsm
71275abffe fix(FN-7575): post release version lines on the surface that actually comments
FN-7575 (issue #1916) added "Current version:" / "Target release:" lines to
GitHubIssueCommentService, but that service is gated on `githubCommentOnDone`
— default false, with no Settings UI — so it effectively never fires. The
"✅ Done —" comments on linked issues are posted by GitHubTrackingCommentService,
which had no version logic. The lines were invisible in production for ~10 days;
issue #1916's own close comment is the proof.

Extract the self-repo check and next-minor computation into a shared
fusion-release-version.ts and apply it across all four done-comment surfaces
(GitHub/GitLab x tracking/issue) so they cannot drift again.

- Release lines join `optionalLines` rather than being appended to the finished
  string, so they count against DONE_COMMENT_MAX_LENGTH and shrink the title
  budget; appending would silently blow the cap on long titles.
- Version resolution is a lazy resolver, so getCliPackageVersion()'s filesystem
  walk only runs for self-repo comments.
- GitLab self-repo matching uses item.projectPath: resolveGitLabTargetFromItem()
  prefers the numeric projectId, which never matches the slug.
- Non-self repos stay byte-for-byte unchanged (asserted).

Per the Surface Enumeration rule, regression tests assert the invariant across
every done-comment surface — both the pure formatters and the services that
post — plus case-insensitive slug matching (issue #1916 is "Runfusion/Fusion"),
in-progress transitions, the 0.0.0 sentinel, unparseable versions, the
lazy-resolution guarantee, and the truncation ladder under the length cap.
Verified non-vacuous: 10 of the new tests fail against the pre-fix source.

Fusion-Task-Id: FN-7575

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 19:07:51 -07:00
gsxdsm
7cec078054 FN-8016: scope task popups to their opening view
Scope task-detail popups to their origin dashboard view by default.

- Default per-view popup scoping while retaining a legacy global-popup opt-out.
- Key popup lifecycle, navigation, and Escape dismissal by task and origin view.
- Update settings copy, documentation, localization, and regression coverage.

Files changed:
 .changeset/fn-8016-task-popup-view-scoping.md      |   7 ++
 docs/dashboard-guide.md                            |   4 +-
 .../core/src/__tests__/settings-defaults.test.ts   |   4 +-
 packages/core/src/settings-schema.ts               |   6 +-
 packages/core/src/types.ts                         |   6 +-
 packages/dashboard/app/App.tsx                     |  67 ++++++-----
 .../app/__tests__/App.keyboard-shortcuts.test.tsx  |  14 ++-
 .../app/__tests__/App.taskPopupViewGating.test.tsx | 125 +++++++--------------
 .../dashboard/app/components/SettingsModal.tsx     |   2 +-
 .../settings/sections/AppearanceSection.tsx        |   6 +-
 .../sections/__tests__/AppearanceSection.test.tsx  |  18 ++-
 .../app/hooks/__tests__/useAppSettings.test.ts     |  15 +++
 .../app/hooks/__tests__/usePoppedOutTasks.test.ts  |  28 ++---
 packages/dashboard/app/hooks/useAppSettings.ts     |   8 +-
 packages/dashboard/app/hooks/usePoppedOutTasks.ts  |  14 +--
 packages/i18n/locales/en/app.json                  |   4 +-
 packages/i18n/src/resources.d.ts                   |   4 +-
 17 files changed, 158 insertions(+), 174 deletions(-)

Fusion-Task-Id: FN-8016

Fusion-Task-Lineage: e33beeae-0ce3-4202-95dc-6fb2d26f9770

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 19:03:44 -07:00
gsxdsm
66ae82af5b FN-8007: align concurrency current-use markers
Align dashboard and footer concurrency markers with their native range thumbs.

- Map running counts in min-relative slider coordinates and clamp them to the configured cap
- Standardize native slider thumb dimensions and marker geometry across browsers
- Add dashboard coverage and document the marker behavior

Files changed:
 .changeset/fn-8007-concurrency-dot-alignment.md    |   7 +
 docs/dashboard-guide.md                            |   8 +-
 .../dashboard/app/components/EngineControlMenu.css |  20 ++-
 .../dashboard/app/components/EngineControlMenu.tsx |  19 ++-
 .../__tests__/EngineControlMenu.test.tsx           |  96 +++++-------
 .../command-center/CommandCenterControls.css       |  22 ++-
 .../command-center/CommandCenterControls.tsx       |  19 ++-
 .../__tests__/CommandCenterControls.test.tsx       | 164 +++++++++++++++++++++
 8 files changed, 277 insertions(+), 78 deletions(-)

Fusion-Task-Id: FN-8007

Fusion-Task-Lineage: 9ad8ee0b-09da-413e-96bc-530c897cb32e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 18:43:37 -07:00
gsxdsm
05e499b019 fix(dashboard): make import auto-translate settings findable, native, and non-blocking (#2147)
Follow-up to #2141 (merged). Three operator-reported problems with the
shipped import auto-translate feature, plus a real bug found while
testing them.

## 1. The settings were unfindable

> "where are the translate settings? I can't find them and search in
settings isn't finding them"

The controls rendered fine — but Settings **search** only matches
curated keywords and advertised i18n keys per section, and "Project
General" says nothing about translation. So searching `translate`
matched nothing.

Now advertised on **Project General** (where the controls live) and on
**Project/Global Models** (where the lane is picked). Verified against
the real `filterSettingsSectionsForSearch`, not by eye:

| query | surfaces |
|---|---|
| `translate` / `translation` | general, project-models, global-models |
| `auto translate`, `target language` | general |

For reference, they live in **Settings → Project General**, directly
below "Always link imported GitHub issues to GitHub tracking".

## 2. The checkbox looked foreign

> "the auto translate checkbox needs to be the left of the text and it
needs to be styled like other check boxes"

It used `SettingsToggleRow`, which renders a **right-aligned toggle
switch**, while every other GitHub/import setting in that section uses a
plain `checkbox-label` with the input **before** its text. Two checkbox
idioms in one section read as a bug regardless of which is nicer in
isolation.

Both controls now use the section's native `form-group` +
`checkbox-label` / `select.select` markup. A test asserts my checkbox's
class and structure are **identical to the neighbouring
`githubLinkImportedIssuesToTracking` checkbox**, so it can't silently
drift back.

## 3. Auto-translation was async but not incremental

> "ensure the auto translate is non blocking and runs async in the
background"

The list never blocked (it rendered originals immediately; import is
cache-read only). But a **single request translated all 50 issues**, so
nothing appeared until every issue finished — minutes on a large page —
and one timeout discarded the whole page's work.

It now streams in chunks of 8: titles appear as each chunk lands, a
failure costs one chunk instead of the page, and chunks are sequential
so opening the panel can't fan 50 model calls at the provider at once.

## Also: a real infinite-render loop (found by testing #3)

`items` and `eligible` are fresh **array identities** on most renders,
and both sat in the effect's dependency list — effect → `setState` →
re-render → new array → effect. It manifested as a **heap OOM** under
`renderHook`.

Effect dependencies are now string/scalar only, with live issue data
read from a ref, and the reset path preserves state identity so it
cannot re-trigger itself. This bug shipped in #2141; it needed a
re-render with a fresh `issues` identity to trigger, but it was live.

## Changeset

Folded into the **existing unreleased** `github-import-auto-translate`
changeset rather than adding a second one for the same unshipped
feature.

## Verification

- ✅ `pnpm lint`, root `pnpm typecheck`, `pnpm verify:fast`, `pnpm
test:gate` (479 tests)
- ✅ 54 tests across the translate suites, including new coverage pinning
the checkbox idiom + neighbour parity, and that translations **stream
per chunk** rather than all-or-nothing (a regression to one request
fails these)

## Reviewer note

Worth knowing for future test-writing here: `beforeEach(() =>
mock.mockReset())` **implicitly returns the mock**, and vitest treats a
function returned from `beforeEach` as a teardown callback — it then
invokes the mock with zero arguments and corrupts `mock.calls`. The test
file uses a block body and says why.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added project settings to enable GitHub import auto-translation and
choose a target language, including “follow dashboard language”.
* Auto-translation now runs in the background, chunked, and streams
translated issue content progressively into imported tasks.
* Translations are cached and reused to speed up repeated imports, with
incremental updates shown as they arrive.
* Settings search now includes translation and import auto-translation
terms.

* **Bug Fixes**
* Improved update handling so changes to issue text refresh
translations, while closed issues are never requested and failures don’t
erase already translated results.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 18:08:32 -07:00
gsxdsm
3f426c3ef0 fix: isolate mDNS node broadcasts (#2155)
## Summary

- Make Fusion mDNS broadcast names node-unique to avoid same-name DNS-SD
collisions.
- Treat asynchronous Bonjour broadcast errors as non-fatal diagnostics
when no listener is registered.
- Add regression coverage for a service-name collision.

## Validation

- `pnpm --filter @fusion/core exec vitest run
src/__tests__/node-discovery.test.ts --silent=passed-only
--reporter=dot`
- `pnpm --filter @fusion/core typecheck`
2026-07-15 18:08:07 -07:00
Phil Larson
514ccd304c Recover malformed agent interview responses (#2146)
## Summary
- preserve valid onboarding JSON returned in Pi thinking-only assistant
blocks
- retry one bounded JSON-only reformat turn when the model returns prose
or malformed output
- keep streamed output as a final extraction fallback instead of
overwriting it with an empty content array

## Verification
- `pnpm --filter @fusion/dashboard exec vitest run
src/__tests__/agent-onboarding.test.ts` — 20 passed
- `pnpm --filter @fusion/dashboard typecheck`
- `pnpm lint`
- `pnpm check:changesets --strict`
- live local-runtime AI Interview produced a structured
Hermes/computer-use onboarding question after restart

Follow-up to #2142, which fixed the missing planning-model fallback and
runtime-hint prompt.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved agent onboarding recovery when assistant replies include
thinking-only content or malformed JSON.
* Added a single automatic retry that re-formats invalid output into
valid onboarding JSON.
* Preserved structured “thinking” content as part of valid onboarding
responses.
* Normalized optional onboarding fields so null/empty/whitespace-only
values are treated as missing.
* Tightened Hermes automation so the runtime hint is set exactly to
`hermes`.
* **Tests**
* Added onboarding event synchronization and expanded coverage for
recovery and field normalization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 18:07:37 -07:00
gsxdsm
40ae6ddb3a fix(FN-8024): stop logging skipped stale triage recovery writes
Skipping a stale planning-state write is the expected outcome of a normal
scheduler advancement, not an anomaly, so the warn was pure log noise.
Behavior is unchanged; only the two planLog.warn emissions are removed.

Fusion-Task-Id: FN-8024

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 17:35:32 -07:00
gsxdsm
7daa16fe30 FN-8002: suppress reconnecting hint on planning question screen
Gate Planning Mode's "Reconnecting…" indicator to the loading view so persisted awaiting-input questions stay free of transient SSE reconnect noise.

- Show planning.reconnecting only when view.type is "loading"
- Cover desktop and mobile resumed question screens without the hint
- Keep the hint during active generation loading reconnects
- Add patch changeset for the user-facing fix

Files changed:
 .changeset/FN-8002-planning-reconnecting-hint.md   |  7 +++
 .../dashboard/app/components/PlanningModeModal.tsx |  6 +-
 .../PlanningModeModal.planning-flow.test.tsx       | 73 ++++++++++++++++++++++
 3 files changed, 85 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-8002

Fusion-Task-Lineage: 0a8682ee-922d-4acd-b7ce-bbf4f25dfce7

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 17:12:32 -07:00
gsxdsm
22fde62510 FN-8001: open footer planning sessions in Planning view
Navigate Background Tasks footer planning rows into the embedded Planning view so resume actually loads planning mode.

- Call handleChangeTaskView("planning") when opening a background planning session
- Extend App tests for footer planning resume and unchanged non-planning session routes
- Update dashboard-guide planning resume entry-point docs

Files changed:
 docs/dashboard-guide.md                            |  4 +-
 packages/dashboard/app/App.tsx                     |  5 ++
 .../app/components/__tests__/App.test.tsx          | 94 +++++++++++++++++++---
 3 files changed, 91 insertions(+), 12 deletions(-)

Fusion-Task-Id: FN-8001

Fusion-Task-Lineage: 402ece21-5f29-4304-a3aa-ef7004a30155

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:58:56 -07:00
gsxdsm
0b06026c74 FN-7992: open GitHub import issue/PR details in FloatingWindow
Show GitHub/GitLab import item details in a draggable FloatingWindow instead of an embedded two-pane preview, simplifying the import modal layout.

- Replace inline list/preview split with FloatingWindow for issue and PR detail
- Remove two-pane resize handle, mobile list/preview switch, and related CSS
- Keep close confirmation when discarding detail-window changes
- Update FloatingWindow styles and dashboard guide for floating import details
- Slim GitHubImportModal tests while restoring core import-modal coverage

Files changed:
 docs/dashboard-guide.md                            |   6 +-
 .../dashboard/app/components/FloatingWindow.css    |  23 +-
 .../dashboard/app/components/GitHubImportModal.css | 359 +-------
 .../dashboard/app/components/GitHubImportModal.tsx | 347 ++------
 .../components/__tests__/FloatingWindow.test.tsx   |   2 +-
 .../__tests__/GitHubImportModal.test.tsx           | 909 ++-------------------
 6 files changed, 139 insertions(+), 1507 deletions(-)

Fusion-Task-Id: FN-7992

Fusion-Task-Lineage: 0991e28c-d793-4a41-9312-6e250e8a09c4

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:51:34 -07:00
gsxdsm
c0bef0bfbe FN-7969: deprecate unused builtin Coding (Ideas) workflow
Hide builtin:coding-ideas from new selection after occupancy preflight, while keeping it resolvable for any existing task selections.

- Add builtin:coding-ideas to DEPRECATED_BUILTIN_WORKFLOW_IDS so it is excluded from defaultEnabledBuiltinWorkflowIds and listWorkflowDefinitions selection listings
- Keep getBuiltinWorkflow / direct resolution working for pre-existing Coding (Ideas) task selections
- Document deprecation and custom-workflow copy path in dashboard-guide and workflow-steps
- Extend builtin-workflows and settings-sections tests for hide-from-selection + management/resolution retention
- Add minor changeset for @runfusion/fusion

Files changed:
 .changeset/fn-7969-deprecate-coding-ideas.md       |  7 +++++++
 docs/dashboard-guide.md                            |  2 +-
 docs/workflow-steps.md                             |  2 +-
 .../core/src/__tests__/builtin-workflows.test.ts   | 28 ++++++++++++++--------
 packages/core/src/builtin-workflows.ts             |  9 +++----
 packages/core/src/types.ts                         |  9 ++++---
 .../app/__tests__/settings-sections.test.tsx       |  2 ++
 7 files changed, 43 insertions(+), 16 deletions(-)

Fusion-Task-Id: FN-7969

Fusion-Task-Lineage: 578ae727-e1b6-4ff9-a3a2-d1228c50fba6

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:47:01 -07:00
gsxdsm
f0485041c1 FN-8000: open mobile chat threads before streaming back-btn assertions
Fix ChatView mobile streaming tests that failed when chat-back-btn was missing after remount with an active session restored by useChat.

- Open the session via sidebar click before asserting chat-back-btn in streaming mobile tests
- Populate sessions/filteredSessions fixtures for the silent-request mobile case
- Assert empty-state copy stays hidden once the thread is open
- Document remount/sidebar restore requirement with FNXC comment

Files changed:
 packages/dashboard/app/components/__tests__/ChatView.core-interactions.test.tsx | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-8000

Fusion-Task-Lineage: 8893884c-b5ed-4c5a-8962-31688b859b9a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:44:00 -07:00
gsxdsm
96b1f21707 FN-7999: show failed-banner diagnostics and model/node retry
Expose a richer Task Failed banner with tool-error diagnostics and one-click retry using a different model or node.

- Always show the failed banner for failed tasks (including errorless failures) with a generic reason fallback
- Surface the latest agent-log tool_error detail and a retry hint for workflow/step-execute failures
- Add Retry and Retry with a different model/node actions with deferred model/node override save on confirm
- Style the banner recovery controls and cover them in TaskDetailModal tests
- Add minor changeset for @runfusion/fusion

Files changed:
 .changeset/fn-7999-failed-banner-retry.md          |   7 +
 .../dashboard/app/components/TaskDetailModal.css   |  47 +++++++
 .../dashboard/app/components/TaskDetailModal.tsx   | 143 ++++++++++++++++++++-
 .../__tests__/TaskDetailModal.test-helpers.ts      |   3 +-
 .../components/__tests__/TaskDetailModal.test.tsx  |  59 ++++++++-
 5 files changed, 248 insertions(+), 11 deletions(-)

Fusion-Task-Id: FN-7999

Fusion-Task-Lineage: d4268c43-442f-4f39-afbe-c6f583ec0fc0

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:35:26 -07:00
gsxdsm
0e84731d8a fix(FN-7965): let the overseer see executor-stage failures
`deriveSignalAndSources`'s executor branch never read `task.status`, so a row
parked `status: "failed"` — e.g. the terminal fn_task_done refusal/invariant
park — reported `signal: "progressing"` with the reason "Task is actively
executing in-progress work". The overseer observed a dead task as healthy and
took no action. `failed` was only ever derived for the merger/pull-request
stages, so the sole backstop was the FN-7743 2h stall proxy firing hours later.
This is exactly what FN-7965's audit trail shows: every intervention on a
terminally-parked task was action="observe", reason="Task is actively executing
in-progress work".

Report `failed` so recovery engages on the next poll. This adds no new policy:
a failed executor observation already routes to `retry_step` (executor sources
are `agent-log`, never an ERROR_SOURCE_KIND), bounded by
PLANNER_RECOVERY_MAX_ATTEMPTS and escalated on exhaustion.

Precedence and dedup preserved: `paused` still wins, so an operator/user-paused
row stays `blocked` and is never routed into autonomous recovery; and the reason
is a constant (never interpolating task.error/status) so the FN-7577
`stage|signal|reason` feed dedup still suppresses repeat observations.

Verified: the repro test fails with the branch disabled; paused-precedence,
healthy-card (FN-7577) and dedup guards added; overseer/recovery surfaces
93 passed + core planner-recovery 20/20; engine + dashboard typecheck clean;
`pnpm test:gate` green (294+122+63).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 16:32:17 -07:00
gsxdsm
214af98591 FN-7977: hold Plan Review provider failures without replan regression
Prevent provider, model, transport, and abort failures from bouncing tasks back to planning after they enter execution.

- Classify non-plan-defect Plan Review failures and skip needs-replan handoff
- Terminate graph traversal with plan-review-provider-failure-hold and retry in place
- Guard triage recovery so advanced column/worktree/step state is never overwritten
- Document planning-recovery no-regression invariant and add regression tests
- Add patch changeset for the operator-facing fix

Files changed:
 .changeset/fn-7977-planning-failure-no-regression.md |   7 ++
 docs/architecture.md                               |   1 +
 docs/workflow-steps.md                             |   2 +-
 packages/engine/src/__tests__/replan-target.test.ts     |  17 +++-
 packages/engine/src/__tests__/transient-error-detector.test.ts |  32 +++++-
 packages/engine/src/__tests__/triage.test.ts       | 110 +++++++++++++++++++++
 packages/engine/src/__tests__/workflow-graph-optional-group.test.ts          |  46 ++++++++-
 packages/engine/src/__tests__/workflow-graph-optional-step-fix.test.ts       |  36 +++++++
 packages/engine/src/executor.ts                    |  62 +++++++++++-
 packages/engine/src/replan-target.ts               |  22 +++++
 packages/engine/src/transient-error-detector.ts    |  37 +++++++
 packages/engine/src/triage.ts                      |  73 +++++++++++---
 packages/engine/src/workflow-graph-executor.ts     |  45 ++++++++-
 13 files changed, 466 insertions(+), 24 deletions(-)

Fusion-Task-Id: FN-7977

Fusion-Task-Lineage: 6d62d3ca-c6f3-4d02-a377-d7fd59f0c0f9

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:29:10 -07:00
gsxdsm
1c02e683b7 FN-7970: deprecate unused builtin:brainstorming from new selection
Hide the built-in Brainstorming workflow from new selection after occupancy preflight, while keeping it resolvable for existing tasks.

- Add DEPRECATED_BUILTIN_WORKFLOW_IDS and isBuiltinWorkflowDeprecated helper
- Exclude deprecated built-ins from defaults and selection listings
- Hide deprecated built-ins from Settings workflow enablement toggles
- Update docs/tests and add a minor changeset for the operator-facing change

Files changed:
 .changeset/fn-7970-deprecate-brainstorming.md      |  7 ++++
 docs/workflow-steps.md                             |  2 +-
 .../core/src/__tests__/builtin-workflows.test.ts   | 40 ++++++++++++----------
 packages/core/src/builtin-workflows.ts             | 17 ++++++++-
 packages/core/src/index.gate.ts                    |  2 ++
 packages/core/src/index.ts                         |  2 ++
 packages/core/src/task-store/remaining-ops-8.ts    | 10 ++++--
 packages/core/src/types.ts                         |  9 +++++
 .../app/__tests__/settings-sections.test.tsx       | 28 ++++++++++++++-
 .../settings/sections/GeneralSection.tsx           |  9 +++--
 10 files changed, 101 insertions(+), 25 deletions(-)

Fusion-Task-Id: FN-7970

Fusion-Task-Lineage: 47f9cd6e-d843-4c14-b197-447ff2072e3b

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:24:23 -07:00
gsxdsm
92a5587797 fix(FN-7987): restore chat.test.ts engine mock completeness
FN-7987 added the shared fusion toolset imports to chat.ts without extending
chat.test.ts's hardcoded `vi.mock("@fusion/engine")` factory, which red-lit
`check-mock-completeness` and blocked the whole merge gate on main.

Add the 11 missing exports with shapes matching their call sites: the singular
`create*Tool` factories return one tool each, while the plural factories are
spread (and `createMemoryTools(...)` is `.filter`ed by `tool.name`), so they
return arrays.

Test-only; no changeset. Verified: both mock-completeness checks green,
chat.test.ts 14/14, and `pnpm test:gate` now passes end-to-end (294+122+63).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 16:21:10 -07:00
gsxdsm
f1b528f4c5 fix(FN-7965): honor terminal fn_task_done park instead of resurrecting the session
The in-session `fn_task_done` handler parks a task terminally (status=failed,
worktree/branch/sessionFile cleared) once its refusal/invariant retry budget is
exhausted. That write happens inside the live agent session, so the executor's
no-fn_task_done retry loop never observed it and spawned a fresh session anyway.
The retry completed, marked the task done, and dragged a worktree-less row into
the pre-merge graph, where the first write-capable node failed on
`no-worktree-for-write-node` — surfacing as a misleading "Workflow graph
terminated with failure at node 'code-review-remediation'" instead of the real
refusal. Observed on FN-7965 and again live on FN-7981.

Re-read state at the top of the retry loop and honor the park. The status probe
covers all three park sites (invariant-check, explicit refusal, implicit
refusal) rather than the single reported repro.

Deliberately not routed through the FN-4806 reclaim branch: its silent todo
requeue would clear the park and, with the budget already spent, re-park on the
next pickup in a todo->execute->park loop.

The pre-existing reclaim probes could not catch this — they test
`worktree === null`, but the store maps a cleared column to `undefined`
(`task-store/serialization.ts`: `row.worktree || undefined`), so the existing
test only passed because its mock returned a value production never emits.
Tightening that probe regressed 7 fixtures and is left as separate work.

Verified: new tests fail with the guard disabled; engine reliability surfaces
show zero regressions vs baseline (17 pre-existing failures unchanged, 495->499
passing); engine-core gate suite 294/294.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 16:21:03 -07:00
gsxdsm
363916926d FN-7995: always persist tool_error detail for Activity feed diagnosis
Always persist bounded tool_error detail so the task Activity feed can surface underlying failure messages even when verbose tool-output persistence is off.

- Keep tool args and successful tool_result detail opt-in via persistAgentToolOutput
- Always include bounded tool_error detail in agent-log JSONL rows
- Document diagnostic retention in types, agent-logger, and storage docs
- Cover Activity reveal behavior and logger persistence with unit tests
- Add patch changeset for operator-facing Activity error detail fix

Files changed:
 .changeset/fn-7995-tool-error-detail.md            |  7 ++++
 docs/storage.md                                    |  1 +
 packages/core/src/agent-log-constants.ts           |  4 +++
 packages/core/src/types.ts                         | 10 ++++--
 .../app/components/__tests__/TaskChatTab.test.tsx  | 42 ++++++++++++++++++++++
 packages/engine/src/__tests__/agent-logger.test.ts | 41 ++++++++++++++++++---
 packages/engine/src/agent-logger.ts                |  9 ++---
 7 files changed, 104 insertions(+), 10 deletions(-)

Fusion-Task-Id: FN-7995

Fusion-Task-Lineage: 0fa063df-58b1-4991-a0d9-e8a77181d32a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 16:17:37 -07:00
gsxdsm
3d658059cb FN-7979: add route regressions for translated GitHub issue import
Lock GitHub import routes so tasks use cached translated title/body with source URL appended, and fail open to original prose when translation is off, missing, or for closed issues.

- Mock getImportTranslation on the route test store
- Cover single-issue and batch import happy paths for cached translations
- Assert fail-open when auto-translate is off, cache misses, or issue is closed
- Prefer persistent getSettings mocks so translation settings survive multi-call import flows
- Document CLI/GitLab exclusion in FNXC surface note

Files changed:
 .../dashboard/src/__tests__/routes-github.test.ts  | 216 ++++++++++++++++++++-
 1 file changed, 214 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7979

Fusion-Task-Lineage: 7b0fe0a5-44f3-4131-b535-6b4c7602b5a8

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:58:55 -07:00
gsxdsm
e83116a970 FN-7991: mark import-screen items as imported immediately
Mark successful GitHub/GitLab import rows as Imported right away via optimistic local URL state, without waiting for the parent tasks prop round-trip.

- Add optimisticImportedUrls unioned with tasks-derived importedUrls via isUrlImported
- Populate on successful GitHub issue/PR and GitLab imports; clear on modal reset and source change
- Disable re-import and show Imported badge on rows, counts, and import buttons for optimistic URLs
- Cover optimistic import surfaces in GitHubImportModal tests
- Document the behavior in the dashboard guide and add a patch changeset

Files changed:
 .changeset/fn-7991-import-screen-optimistic-imported.md   |  7 +++
 docs/dashboard-guide.md                                    |  2 +-
 packages/dashboard/app/components/GitHubImportModal.tsx    | 57 +++++++++++++++++-----
 packages/dashboard/app/components/__tests__/GitHubImportModal.test.tsx | 56 ++++++++++++++++++---
 4 files changed, 103 insertions(+), 19 deletions(-)

Fusion-Task-Id: FN-7991

Fusion-Task-Lineage: ddfb249a-e2e8-4723-a86d-7f6edc74305c

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:51:54 -07:00
gsxdsm
80f202831d FN-7994: keep planning session sidebar populated during load
Speed up Planning mode session-list load so the sidebar never blanks while history refreshes.

- Seed the planning sidebar from already-loaded active sessions via initialSessions
- Filter GET /ai-sessions and store listAll by optional type=planning to skip non-planning payloads
- Show skeleton rows while the first authoritative session refresh is in flight
- Wire type through client fetchAiSessions, dashboard AiSessionStore, and core listAllAiSessions
- Add UI and route coverage for seeded/skeleton load and type-filtered listing
- Ship patch changeset for the operator-facing performance fix

Files changed:
 .changeset/FN-7994-planning-sidebar-fast-load.md   |  7 +++
 packages/core/src/async-ai-session-store.ts        |  7 ++-
 packages/dashboard/app/App.tsx                     |  1 +
 packages/dashboard/app/api/legacy.ts               |  3 +-
 .../dashboard/app/components/PlanningModeModal.css | 48 ++++++++++++----
 .../dashboard/app/components/PlanningModeModal.tsx | 27 ++++++++-
 .../PlanningModeModal.planning-flow.test.tsx       | 65 ++++++++++++++++++++++
 .../app/components/dashboard/MainContent.tsx       |  2 +
 .../dashboard/app/components/dashboard/types.ts    |  2 +
 .../src/__tests__/routes-planning.test.ts          | 29 ++++++++++
 packages/dashboard/src/ai-session-store.ts         |  2 +-
 packages/dashboard/src/routes.ts                   | 16 +++++-
 12 files changed, 201 insertions(+), 8 deletions(-)

Fusion-Task-Id: FN-7994

Fusion-Task-Lineage: 7c4cf98d-6dfe-4b9b-bc88-62257ed39507

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:49:18 -07:00
gsxdsm
dc7bb40948 FN-7990: share worktree classifier so Code Review acquires a worktree
Unify write-capability classification so graph preparation acquires a worktree for inline-fix Code Review before runtime runs, eliminating the immediate no-worktree-for-write-node failure.

- Add shared workflowNodeRequiresWorktree helper for preparation and runtime
- Plumb optional-group context and reviewerInlineFixes into graph preparation
- Acquire/reuse/reacquire worktrees for write-capable inline review nodes
- Keep Plan Review and disabled inline fixes read-only
- Add regression tests and a patch changeset

Files changed:
 .changeset/fn-7990-code-review-worktree.md         |  7 ++
 .../__tests__/ce-workflow-step-executor.test.ts    | 97 ++++++++++++++++++++++
 .../workflow-node-execution-needs.test.ts          | 47 +++++++++++
 packages/engine/src/executor.ts                    | 32 +++----
 packages/engine/src/workflow-graph-executor.ts     | 52 ++++++++----
 .../engine/src/workflow-node-execution-needs.ts    | 46 ++++++++++
 6 files changed, 243 insertions(+), 38 deletions(-)

Fusion-Task-Id: FN-7990

Fusion-Task-Lineage: f5d19181-0b98-4827-8adb-069f7dc05c03

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:34:43 -07:00
gsxdsm
5ff7a20738 FN-7976: fix mailbox artifact open and view-task popups
Fix Mailbox/Artifacts media auth and ensure View task always opens a usable popup.

- Add artifactMediaUrlWithToken for authenticated img/video/audio/link loads while keeping artifactMediaUrl token-free for fetch and HTML previews
- Load script-capable HTML artifact previews via Authorization + revocable blob URL so tokens never reach allow-scripts iframes
- Keep non-board/list task popups (Mailbox, Documents) visible even when board/list-only popup gating is enabled
- Upgrade duplicate popOut entries so reopening a task refreshes snapshot and origin
- Document the behavior and add a patch changeset

Files changed:
 .changeset/fn-7976-mailbox-artifact-fixes.md       |  7 +++
 docs/dashboard-guide.md                            |  2 +-
 packages/dashboard/app/App.tsx                     | 15 +++--
 .../app/__tests__/App.taskPopupViewGating.test.tsx | 10 ++-
 .../dashboard/app/__tests__/api-artifacts.test.ts  | 12 +++-
 .../api/__tests__/legacy-artifact-media.test.ts    | 27 ++++++++
 packages/dashboard/app/api/legacy.ts               | 21 +++++--
 .../dashboard/app/components/ArtifactsGallery.tsx  | 72 ++++++++++++++++++----
 .../dashboard/app/components/DocumentsView.tsx     |  4 +-
 .../app/components/MailboxArtifactAttachment.tsx   |  6 +-
 .../dashboard/app/components/TaskDocumentsTab.tsx  |  6 +-
 .../components/__tests__/DocumentsView.test.tsx    | 31 ++++++----
 .../__tests__/MailboxArtifactAttachment.test.tsx   | 24 ++++----
 .../app/components/__tests__/MailboxView.test.tsx  |  8 +--
 .../components/__tests__/TaskDocumentsTab.test.tsx | 16 ++---
 .../app/hooks/__tests__/usePoppedOutTasks.test.ts  |  9 ++-
 packages/dashboard/app/hooks/usePoppedOutTasks.ts  | 17 +++--
 17 files changed, 206 insertions(+), 81 deletions(-)

Fusion-Task-Id: FN-7976

Fusion-Task-Lineage: 4c25b3a6-5836-4629-b33e-647f213e3261

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:30:55 -07:00
gsxdsm
667f4c8a55 FN-7987: expose shared fusion toolset to chat agents and Grok CLI
Give dashboard chat and room responders the same safe coordination/productivity tools as other agent lanes, including via the Grok MCP bridge.

- Export chat coordination tool factories from @fusion/engine for public use
- Assemble createChatFusionToolset with board, delegation, web, goal, memory, and research tools
- Wire the shared toolset into model-loop chat and room-responder sessions
- Exclude destructive agent-lifecycle tools and fn_memory_append from chat
- Cover chat fusion parity and Grok bridge tool preservation with tests
- Document chat Grok tool parity and add a minor changeset

Files changed:
 .changeset/fn-7987-chat-fusion-toolset.md          |  7 ++
 docs/agents.md                                     |  1 +
 docs/grok-cli-contract.md                          |  2 +-
 packages/dashboard/src/__tests__/chat-manager.test.ts | 52 +++++++++++-
 packages/dashboard/src/chat.ts                     | 95 +++++++++++++++++++++-
 packages/engine/src/__tests__/agent-session-helpers.test.ts | 15 ++++
 packages/engine/src/index.ts                       | 26 ++++++
 plugins/fusion-plugin-grok-runtime/src/__tests__/tool-bridge.test.ts | 36 ++++++++
 8 files changed, 230 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-7987

Fusion-Task-Lineage: 4d8d3dbc-10b8-4b56-9b63-79fe85a13755

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:25:39 -07:00
gsxdsm
d9843f75bc FN-7983: colocate project summarization model with summarization settings
Move the Project Summarization model lane and title-summarizer fallback next to the AI title/commit summarization controls in Project Models.

- Extract shared project-lane renderer and keep default/merger/import-translate in the general Model Lanes list
- Render summarization + title-summarizer fallback inside the AI summarization section with the same models-available guard
- Add regression tests for colocation and empty-models guard
- Update settings reference docs and add a patch changeset

Files changed:
 .changeset/fn-7983-summarization-lane-colocation.md       |   7 ++
 docs/settings-reference.md                         |   6 +-
 .../app/__tests__/settings-sections.test.tsx       |  61 ++++++++++
 .../settings/sections/ProjectModelsSection.tsx     | 126 ++++++++++++---------
 4 files changed, 142 insertions(+), 58 deletions(-)

Fusion-Task-Id: FN-7983

Fusion-Task-Lineage: c473ba61-f003-401d-bc66-86f2078ba047

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:21:16 -07:00
gsxdsm
10b80453c4 FN-7978: share GitHub import dedup via sourceIssue-first helper
Unify GitHub issue import deduplication so prior imports stay marked after description edits or owner/repo casing changes.

- Extract shared buildGitHubIssueSource and isGitHubIssueAlreadyImported helpers in dashboard github.ts (sourceIssue-first, case-insensitive repo, sourceMetadata + description URL fallbacks)
- Route CLI import paths, extension tools, and dashboard single/batch import through the shared helpers
- Drop local description-URL-regex-only importedUrls dedup; list existing tasks with slim:false for full provenance
- Add regression coverage and changeset for the operator-facing fix

Files changed:
 .changeset/fn-7978-github-import-dedup.md          |  7 ++
 docs/gitlab-parity-inventory.md                    |  2 +-
 packages/cli/src/__tests__/extension.test.ts       | 12 ++--
 .../task-command-github-import-tracking.test.ts    |  6 ++
 packages/cli/src/commands/__tests__/task.test.ts   | 36 +++++++---
 packages/cli/src/commands/task.ts                  | 82 +++++++++-------------
 packages/cli/src/extension.ts                      | 35 ++-------
 packages/dashboard/src/__tests__/github.test.ts    | 22 +++++-
 .../dashboard/src/__tests__/routes-github.test.ts  |  8 +--
 packages/dashboard/src/github.ts                   | 62 +++++++++++++++-
 packages/dashboard/src/index.ts                    |  2 +-
 .../dashboard/src/routes/register-git-github.ts    | 33 +--------
 12 files changed, 174 insertions(+), 133 deletions(-)

Fusion-Task-Id: FN-7978

Fusion-Task-Lineage: 44f3d555-49fb-41e2-87d0-0a722462f132

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 15:16:19 -07:00
gsxdsm
5445693e51 fix(FN-8009): quiet embedded backend TUI logs
Suppress routine embedded-backend resolution messages while retaining redacted external-backend diagnostics.
2026-07-15 14:48:50 -07:00
Phil Larson
ab228551e9 fix(dashboard): use active node registry authority (#2145)
## Summary

- reuse the dashboard server's initialized `centralCore` for all
node-management routes
- preserve the legacy fallback only when no shared central authority is
provided
- never close the server-owned central authority from an individual
request
- cover node list and registration with regression tests that fail if a
route constructs its own `CentralCore`

## Problem

With the dashboard running on the PostgreSQL backend, `/api/nodes` and
`POST /api/nodes` bypassed the server's initialized PostgreSQL-backed
`centralCore` and constructed a separate legacy `CentralCore()`. Reads
could hit the wrong registry, while writes failed with a null SQLite
handle (`Cannot read properties of null (reading 'prepare')`). The same
pattern affected node detail, health, path-mapping, version,
plugin-sync, and Docker-config endpoints.

## Verification

- targeted regression: 2 tests passed
- dashboard typecheck passed
- ESLint passed for changed TypeScript files
- strict changeset validation passed
- dashboard production build passed
- `pnpm test:gate` passed against an isolated PostgreSQL 16 cluster:
  - engine core: 294 tests
  - PostgreSQL gate: 122 tests
  - CLI CI-shape: 63 tests

## Changeset

Patch release for `@runfusion/fusion`.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved multi-node management by keeping node operations connected to
the active PostgreSQL registry.
* Updated node listing, registration, configuration, health, version,
plugin, path, and Docker configuration routes for more consistent
behavior.
* Preserved Docker configuration validation and safe response handling.

* **Tests**
* Added coverage for retrieving and registering nodes through the active
registry.
* Verified successful node creation responses and handling of optional
configuration fields.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 14:45:06 -07:00
gsxdsm
8c3758584b refactor(engine): peel merger glob, test-command, and file-scope satellites (#2143)
## Summary

Wave 2 of package code organization (follows merged #2139): peel
pure/satellite clusters out of `packages/engine/src/merger.ts` while
keeping `merger.js` import paths stable for tests and production.

- **`merger-glob.ts`** — `ConflictType`, `LOCKFILE_PATTERNS`,
`GENERATED_PATTERNS`, `matchGlob`
- **`merger-workspace-test-commands.ts`** — pnpm workspace package
mapping + `inferDefaultTestCommand` / scoped + file-scoped test command
derivation
- **`merger-file-scope.ts`** — `parseDiffStat`, `extractFileScope` /
`matchesScope`, squash file-scope invariant (`FileScopeViolationError`,
assert/enforce with main’s `status` audit semantics and merge-trait
modes)
- Re-exports from `merger.ts`; baseline ratchet for `merger.ts` (~12.5k
→ ~11.8k)

## Test plan

- [x] `pnpm --filter @fusion/engine exec tsc --noEmit`
- [x] eslint on merger + satellites
- [x] `merger-file-scope-invariant.test.ts` (18 tests)
- [x] focused conflict-resolution pattern tests
- [ ] CI merge gate

## Plan

Continues
`docs/plans/2026-07-14-001-refactor-package-code-organization-plan.md`
(U6 merger satellites).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added file-scope validation for merge operations, with strict and
warning-only enforcement options.
* Added automatic classification of lockfile and generated-file
conflicts.
* Added workspace-aware test selection based on changed packages and
files.
* Added support for inferring test commands across pnpm, Yarn, Bun, and
npm projects.

* **Refactor**
* Organized merge, conflict-resolution, file-scope, and test-inference
functionality into dedicated modules without changing existing public
access paths.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 14:44:35 -07:00
gsxdsm
f9c19f9f3a FN-7967: accept custom triage workflow IDs and honor project default
Allow triageDefaultWorkflowId and triageDecisionOnlyWorkflowId to accept custom workflow IDs so project default workflows are honored at triage time.

- Change triage workflow settings from enum to string; empty triageDefaultWorkflowId inherits config.settings.defaultWorkflowId
- Render triage prompt default from project settings unless an explicit stored override exists
- Only pass stored triageDefaultWorkflowId into triage policy settings so declaration defaults do not clobber project defaults
- Document settings behavior and add core/engine regression coverage
- Add patch changeset for @runfusion/fusion

Files changed:
 .changeset/fn-7967-triage-default-workflow.md                        |  7 +++++++
 docs/settings-reference.md                                           |  4 ++--
 packages/core/src/__tests__/builtin-workflow-settings-triage.test.ts | 43 +++++++++++++++++++++++++++++++++++++++++--
 packages/core/src/builtin-workflow-settings.ts                       | 35 ++++++++++++++++++++---------------
 packages/engine/src/__tests__/triage.test.ts                         | 41 +++++++++++++++++++++++++++++++++++++++++
 packages/engine/src/triage.ts                                        | 28 ++++++++++++++++++++++++----
 6 files changed, 135 insertions(+), 23 deletions(-)

Fusion-Task-Id: FN-7967

Fusion-Task-Lineage: e42ea061-889c-4bdd-8a9d-f56f34fc0c89

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:43:55 -07:00
gsxdsm
edac617e10 fix(desktop): assert Linux AppImage embeds Postgres packaging (#2131)
## Summary

Verification of the Fusion Linux AppImage + embedded Postgres packaging
surface (follow-on to #2106 Mac packaging and #2117 Windows PG work).

### What we found

1. **Published `v0.60.0` AppImages are broken for Local/embedded
Postgres** (pre-#2106):
- No `embedded-postgres` / `@embedded-postgres/*` in `app.asar` or
`app.asar.unpacked`
- `package.json` `main` is still `dist/main.js` (no
`main-bootstrap.cjs`)
   - No `omp-runtime` packaged
- `asar.unpacked` only has incidental natives (pi-tui, esbuild,
node-pty)

2. **Current main (post-#2106) packaging config is correct** (verified
via mac `--dir` pack on this host):
   - `main` → `dist/main-bootstrap.cjs`
   - Full `asarUnpack` of `embedded-postgres` + `@embedded-postgres/**`
   - Native bins present under `app.asar.unpacked`
   - `omp-runtime` dist present in asar

3. **Linux arm64 native PG binary smoke**
(`@embedded-postgres/linux-arm64` 15.18) in Docker: initdb → start →
create DB → persist across restart → **OK** (requires postinstall soname
symlinks from `hydrate-symlinks.js` / `pg-symlinks.json`).

4. **Host blocker:** this machine is macOS arm64 — cannot produce or
execute a Linux AppImage end-to-end. Linux packaging must run on
`ubuntu-latest` CI.

### Fix in this PR

Release jobs only checked that `*.AppImage` files existed — which is how
v0.60.0 shipped empty of Postgres. Add:

- `scripts/verify-desktop-linux-pg-packaging.mjs` — inspects
`linux-*-unpacked` trees for:
- `app.asar.unpacked` embedded-postgres + `@embedded-postgres/linux-*`
bins
  - `dist/main-bootstrap.cjs` + `package.json` main
  - `omp-runtime` presence
- Wire into `release.yml` + `test-release.yml` after AppImage artifact
checks
- Unit test lock in `release-workflow.test.ts`

## Test plan

- [x] `pnpm --filter @fusion/core test:embedded-postgres` (33/33 with
60s timeout; default 15s flaked under load)
- [x] Desktop packaging unit tests (`electron-builder-config`,
`build-bundling`, `release-workflow`)
- [x] Inspected published `Fusion-0.60.0-linux-arm64.AppImage` (checksum
OK; PG packaging absent)
- [x] Post-#2106 `electron-builder --mac --dir`: asar.unpacked has PG +
bootstrap + omp
- [x] Docker linux-arm64 native binary lifecycle smoke
- [ ] CI `build-desktop-linux` on this PR (runs the new verifier against
real linux-unpacked)

## Gaps remaining (not fixed here)

| Gap | Notes |
|-----|-------|
| Full AppImage launch + `/api/health` on Linux | Needs Linux host/CI
with display or headless Electron |
| No post-#2106 published AppImage yet | Next release will include
packaging fixes; this PR stops empty AppImages |
| README still says `linux-x64.AppImage` | Actual name is
`linux-x86_64.AppImage` (workflow already correct) |
| Windows packaged Local | Tracked by #2117 / verify-desktop |

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Added verification for Linux AppImage packaging to ensure embedded
Postgres binaries, platform files, symlinks, and runtime assets are
included correctly.
* Confirmed packaged application metadata points to the expected startup
entry point.
* Improved detection of invalid, missing, or incorrectly formatted
packaging artifacts.

* **Tests**
* Added coverage for architecture-specific binaries, exact ASAR paths,
executable files, and symlink metadata.
* Verified packaging checks run after Linux desktop artifacts are
created.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 14:41:05 -07:00
Phil Larson
329fc1f664 fix(dashboard): authenticate artifact media URLs (#2144)
## Summary
- append the existing same-origin daemon token fallback to artifact
media URLs used by image, video, and link navigation
- preserve project scoping and artifact ID encoding
- add a focused regression test and patch changeset

## Root cause
Artifact metadata loads through authenticated `fetch`, but previews and
links use raw browser navigation (`<img src>`, `<video src>`, and
anchors), which cannot attach the dashboard bearer header. The media
endpoint therefore returned `401 Valid bearer token required` even
though the dashboard itself was authenticated.

## Verification
- `pnpm --filter @fusion/dashboard exec vitest run --project
dashboard-app-quality-foundation-api app/__tests__/api-artifacts.test.ts
--reporter=dot`
- `pnpm lint`
- `pnpm --filter @fusion/dashboard typecheck`
- `pnpm check:changesets --strict`
- `pnpm build`
- `FUSION_PG_TEST_URL_BASE=postgresql://plarson@127.0.0.1:55432
VITEST_MAX_WORKERS=1 nix shell nixpkgs#postgresql --command pnpm
test:gate`

The broader dashboard foundation API shard was also attempted but
aborted in Node after repeated unmanaged-file-descriptor warnings; the
focused regression and canonical merge gate both pass.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed protected artifact images and links so they load correctly in
authenticated dashboards.
* Added authentication tokens to generated artifact media URLs for
reliable previews and navigation.

* **Tests**
* Added coverage verifying authenticated artifact media URLs include the
expected token and parameters.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 14:40:38 -07:00
gsxdsm
564187332d fix(FN-8009): prevent CLI probe from blocking dashboard
Resolve direct binary manifests before scanning shim contents so dashboard status polling does not run the expensive shim regex against JavaScript entrypoints.
2026-07-15 14:39:08 -07:00
gsxdsm
e24980ae46 FN-7997: keep task-card agent badge text visible on mobile and narrow cards
Stop hiding agent badge labels as icon-only pills on mobile and narrow task cards; rely on existing ellipsis truncation instead.

- Remove @container and max-width media rules that set .card-agent-badge-text { display: none }
- Document the no-icon-only-label requirement with an FNXC comment
- Add regression coverage that label text stays visible with ellipsis and that CSS no longer hides it

Files changed:
 packages/dashboard/app/components/TaskCard.css     | 15 +++-------
 .../__tests__/TaskCard.badge-wrap.test.tsx         | 33 ++++++++++++++++++++++
 2 files changed, 37 insertions(+), 11 deletions(-)

Fusion-Task-Id: FN-7997

Fusion-Task-Lineage: 3add9a91-adef-4299-a638-d71257d26633

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:31:21 -07:00
gsxdsm
a75b2f4bb8 FN-7980: dismiss mobile task popups on swipe/back without leaving board
Register mobile task popups on the Fusion nav stack so browser Back, iOS edge-swipe, and Android Back close the popup and keep the board/list visible.

- Push a modal nav entry when opening a mobile task popup and clean it up on close
- Route FloatingWindow and shortcut closes through nav-aware popup close
- Add swipe-back tests for board and list popup dismissal
- Document popup Back behavior in the dashboard guide

Files changed:
 docs/dashboard-guide.md                            |  3 +-
 packages/dashboard/app/App.tsx                     | 35 +++++++--
 .../__tests__/TaskDetail.swipe-back.test.tsx       | 84 +++++++++++++++++++++-
 3 files changed, 114 insertions(+), 8 deletions(-)

Fusion-Task-Id: FN-7980

Fusion-Task-Lineage: e321a1df-e271-41c0-81af-3560d759f7bb

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:23:23 -07:00
gsxdsm
d74018ff81 FN-7974: collapse chat thinking blocks by default
Collapse Thinking reasoning blocks by default so chat transcripts stay scannable without manually closing each block.

- Remove the open attribute from TaskChatTab thinking details so blocks start collapsed
- Strengthen ChatView and TaskChatTab tests for collapsed-by-default and expand-on-click across persisted, streaming, and Task Detail surfaces
- Add a patch changeset for the operator-facing transcript UX fix

Files changed:
 .changeset/fn-7974-collapse-thinking.md            |  7 ++++++
 packages/dashboard/app/components/TaskChatTab.tsx  |  6 ++++-
 .../__tests__/ChatView.core-interactions.test.tsx  | 26 +++++++++++++++++-----
 .../app/components/__tests__/TaskChatTab.test.tsx  | 13 +++++++----
 4 files changed, 41 insertions(+), 11 deletions(-)

Fusion-Task-Id: FN-7974

Fusion-Task-Lineage: 7cd9009f-3483-444c-8024-ed6b1cec3b89

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:15:50 -07:00
gsxdsm
2179a61db9 FN-7973: fix mobile concurrency sliders with touch-action none
Restore horizontal concurrency thumb drags on mobile by opting range inputs out of the pan-y ancestor lock.

- Set touch-action:none on Engine Control menu and Command Center concurrency range inputs
- Update geometry/touch contract test to assert none and reject pan-y
- Add patch changeset for the mobile slider fix

Files changed:
 .changeset/fn-7973-mobile-concurrency-sliders.md              |  7 +++++++
 packages/dashboard/app/components/EngineControlMenu.css       |  5 ++++-
 .../app/components/__tests__/EngineControlMenu.test.tsx       | 11 ++++++++---
 .../app/components/command-center/CommandCenterControls.css   |  5 ++++-
 4 files changed, 23 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-7973

Fusion-Task-Lineage: bf36e544-c24f-49ae-beae-b11707be9c79

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:12:43 -07:00
gsxdsm
aa0d8635fa FN-7972: fix agents controls panel overlapping content
Elevate the Agents controls popover stacking so it layers above agent cards and token usage on desktop and mobile.

- Add `.agents-view-primary-actions--controls-open` with z-index 50 when the controls panel is open
- Toggle the elevated class from AgentsView when the panel opens
- Extend AgentsView tests for open-state class and stacking CSS invariant
- Add patch changeset for the operator-facing fix

Files changed:
 .changeset/fn-7972-agents-controls-overlap.md           |  7 +++++++
 packages/dashboard/app/components/AgentsView.css        |  8 ++++++++
 packages/dashboard/app/components/AgentsView.tsx        |  2 +-
 .../app/components/__tests__/AgentsView.test.tsx        | 17 ++++++++++++-----
 4 files changed, 28 insertions(+), 6 deletions(-)

Fusion-Task-Id: FN-7972

Fusion-Task-Lineage: e6cca457-1cd1-4577-83a7-d7de7e484580

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-15 14:10:28 -07:00
gsxdsm
0863c0fb58 feat(dashboard): auto-translate foreign-language GitHub issues on import (#2141)
## Why

The Import Tasks panel routinely lists issues in languages the operator
cannot read. Translation already shipped in #2128, but deliberately
**opt-in and preview-only** — its header comment read *"Translation is
opt-in (never automatic) so import provenance stays faithful until the
operator asks."*

This reverses that decision **behind a default-off setting**, so
operators who never opt in keep byte-faithful import provenance. The
superseded comment is kept and annotated rather than deleted, so the
reason the rule changed stays in the code.

### The structural gap #2128 left

`POST /github/issues/import` accepts only `{owner, repo, issueNumber}`
and **re-fetches the issue server-side**. A translation held in React
state could never reach the created task, and the in-memory cache died
with the modal. That is why the cache here is server-side rather than in
the hook — it's what makes "imported issues carry the translated
version" actually true.

## What operators get

Auto-translate is **off by default**. When enabled:

- The **50 most recent OPEN** foreign-language issues translate on panel
load — **list titles**, not just the preview, so the list reads in your
language before you click anything.
- Translations show **by default**, with a toggle back to the original
(hover a translated list title to see the original).
- Translations **persist until the issue closes**, so re-opening the
panel neither waits nor re-bills.
- **Both single and batch import** carry the translation, so the created
task reads like the preview you approved.
- A **target language** setting (unset = follow the dashboard language)
and a dedicated **model lane**, so you can pin a cheap/fast model
without dragging the summarization lane onto it.

## Notable decisions

| Decision | Why |
|---|---|
| Detect **before** the model | An issue already in the target language
is never sent. Without this, an English repo with the setting on would
bill every issue to return its input unchanged. |
| Detection moved to `@fusion/core` | The panel and the server must not
disagree about which issues are foreign; two copies of a heuristic
drift. |
| Own rate-limit budget | Translation shared a 10/hour budget with
refine/goal-draft. Fanning out per-issue would fail partway **and**
starve refine for the hour. |
| Cache keyed on a **source hash** | An edited issue misses the cache
and re-translates instead of serving stale prose. |
| Import is **cache-read only** | A miss imports the original. Import
must never block on, or fail because of, translation. |
| `project_id` leads the cache PK + full RLS contract | All projects
share one flat `project` schema. `verification_cache`'s PK predates that
discipline; this table does not copy that mistake. |

## Verification

- ✅ `pnpm lint`, `@fusion/core` + `@fusion/dashboard` typecheck
- ✅ `pnpm verify:fast` — build + scoped typecheck + real boot smoke
(`/api/health`)
- ✅ `pnpm test:gate` — 479 tests
- ✅ 19 new tests covering the billing invariants
(off/closed/same-language ⇒ **no model call**), cache hit/miss-on-edit,
the 50 cap, and per-item fail-soft
- ✅ `schema-applier` real-Postgres suite (46 tests) exercises migration
`0010` and its isolation invariant

**Pre-existing failures NOT touched** (confirmed red on `HEAD` before
this branch): `AppearanceSection`'s task-popup test, and two PG-cutover
keys (`sqliteMigrationNotice`, `postgresMigrationInboxMessageSentAt`)
missing description mappings. I left the latter rather than guess an
allowlist entry that could mask a real coverage gap.

## Reviewer notes

- Short Latin-script prose (a one-line Spanish title) rates only
*medium* confidence and won't auto-translate — the existing heuristic is
deliberately conservative so English issues are never billed. CJK
detects regardless of length. The threshold is the knob if you'd rather
bias toward translating.
- The RLS/isolation contract in migration `0010` is the part most worth
a careful look.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:06:42 -07:00
gsxdsm
05151a25db feat: faster dashboard and serve startup (#2132)
## Summary

Speeds up **time-to-HTTP-ready** for `fn dashboard` and `fn serve` after
the PostgreSQL cutover without reintroducing the historical 3s
cwd-engine race that degraded webhooks.

- **Dashboard store share (serve parity):** inject the factory-booted
`TaskStore` as `externalTaskStore` so cwd `ensureEngine` does not open a
second pool; share only when store root matches project working
directory (multi-project safe).
- **Serve multi-project:** stop awaiting `startAll()` before listen;
await only the primary engine; background the rest + reconciliation.
- **Defer non-route-critical engine work:** ordered OAuth (refresh →
monitor), automation schedule syncs, and auto-merge **enqueue** after
the engine handle is returnable.
- **Critical-path merge status clear:** still clear stale
`merging`/`merging-pr` before ready so manual merge is not blocked after
crash.
- **Serve `--paused`:** apply `enginePaused` before
`ensureEngine`/`startAll` (dashboard ordering).
- **Stop safety:** generation counter so deferred tails cannot resume
after `stop()` clears `shuttingDown`.
- **Phase timing:** shared `phaseTime` helper, factory substep logs,
serve time-to-listen.

Plan: `docs/plans/2026-07-14-001-feat-faster-startup-plan.md`

## Test plan

- [x] `packages/engine` — `project-engine-manager.test.ts` (path-matched
external store)
- [x] `packages/engine` — `project-engine-deferred-startup.test.ts`
(status clear, OAuth order, stop generation)
- [x] `packages/cli` — `startup-phase.test.ts`
- [x] `packages/cli` — `serve.test.ts` (60 tests, including `--paused`)
- [ ] Local: warm `fn dashboard` / `fn serve` and compare `startup phase
*` / `time-to-listen` logs
- [ ] `pnpm smoke:boot` (real serve `/api/health` on ephemeral port)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Performance**
* Improved dashboard and serve startup times, including faster
time-to-listen and time-to-ready.
* Moved non-essential background initialization off the critical startup
path.
  * Parallelized dashboard service initialization where possible.

* **Reliability**
  * Improved multi-project startup handling and project selection.
  * Prevented cross-project task-store sharing.
  * Added safer shutdown behavior for partially completed startup.

* **Diagnostics**
* Added startup phase timing logs to help identify performance
bottlenecks.

* **Tests**
* Expanded coverage for deferred startup, shutdown, project isolation,
and startup timing.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 14:01:08 -07:00
Phil Larson
883f38d68f Fix agent AI interview model routing (#2142)
## Summary
- resolve the configured planning model when agent onboarding requests
omit an explicit override
- align the onboarding prompt with supported runtime/model hint fields,
allowing AI-created agents to select runtimes such as Hermes
- refresh the generated GitHub issue import limits required by the
repository sync gate

## Root cause
The agent onboarding route loaded project settings but passed only
request-body model fields. The AI Interview UI omits those fields, so
`createFnAgent` was called with `provider=undefined, model=undefined`;
the session returned no usable assistant JSON. The prompt catalog also
prohibited `runtimeHint` despite the parser and form already supporting
it.

## Verification
- targeted agent onboarding tests: 22 passed
- `pnpm --filter @fusion/core typecheck`
- `pnpm --filter @fusion/dashboard typecheck`
- `pnpm lint`
- `pnpm build`
- `pnpm smoke:boot`
- engine merge-gate subset: 294 passed

Full `pnpm test` reached the PostgreSQL gate but this host has no `psql`
binary, so 23 PostgreSQL suites could not start; this is an environment
prerequisite failure, not a test assertion failure.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Agent onboarding interviews now use the configured planning model when
no override is provided.
* Runtime suggestions and runtime-hint guidance are preserved during
onboarding and reflected in generated configurations.
* On onboarding start streaming, planning provider/model resolution now
comes from settings with stricter override validation, and test mode
continues to take priority.

* **Documentation**
* Updated onboarding prompt guidance to support additional configuration
fields and optional runtime draft hints.
  * Reduced the maximum GitHub issue import/browse limit from 100 to 50.

* **Tests**
* Added coverage for runtime-hints prompting and planning-model override
behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 13:57:15 -07:00
gsxdsm
5caf360a58 fix(desktop): green Windows smoke + Linux AppImage PG packaging checks (#2138)
## Summary
- **Windows CI:** run the embedded Postgres smoke as non-admin
`fusion-pg` (with profile prewarm) so elevated `windows-latest` runners
stop failing with PostgreSQL’s admin-token refusal. Packaging still runs
as the job user.
- **Linux AppImage:** add a packaging content verifier for
`main-bootstrap`, `@embedded-postgres` natives, and `omp-runtime` dist
entrypoints; wire it into `release.yml`, `test-release.yml`, and the
advisory **Desktop packaging** PR lane (after `electron-builder --dir`).
- Fix eslint `no-undef` on bare `URL` in the verifier script (was red on
#2131).

## Context
Desktop packaging on Ubuntu was mostly green; Windows desktop builds and
the AppImage packaging PR (#2131 lint) were the remaining red paths. The
win-pg-diag pivot (run smoke as non-admin) proved green on CI; this
ports that approach without removing main’s elevated-token product path
for end-user “Run as administrator” cases (smoke simply does not take
that path when the process is non-admin).

## Test plan
- [x] `pnpm --filter @fusion/desktop exec vitest run
src/__tests__/release-workflow.test.ts`
- [x] `pnpm exec eslint scripts/verify-desktop-linux-pg-packaging.mjs`
- [ ] Desktop packaging workflow on this PR
- [ ] Desktop Windows Build (workflow_dispatch)
- [ ] Confirm #2131 supersession if this lands the same AppImage checks

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Strengthened Linux desktop AppImage validation to confirm embedded
PostgreSQL artifacts, required binaries, symlink hydration, and the
expected app entrypoints are present after packaging.
* Improved Windows embedded PostgreSQL smoke testing by running under a
non-administrator helper user with a prewarmed profile environment.

* **Tests**
* Added automated packaging/release workflow verification steps (Linux
and Windows) to catch embedded PostgreSQL content regressions earlier,
including during artifact build and release verification.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 13:56:10 -07:00
gsxdsm
599a509d22 refactor: package code organization (god-file peels, wave 1) (#2139)
## Summary

First wave of package-internal code organization: split oversized
modules into domain-named files/folders while preserving public import
paths via re-exports, and refresh the line-count ratchet scoreboard.

- **Plan:**
`docs/plans/2026-07-14-001-refactor-package-code-organization-plan.md`
(multi-wave program; this PR lands U1–U4 + first U3/U6 slices)
- **Core types:** peel `types.ts` into
`types/{board,merge-queue,execution-and-ui,merge-policy,workflow-steps}.ts`
with browser-safe Vite alias preserved
- **Core TaskStore:** rename `remaining-ops-9` →
`task-commit-associations` (domain-named, not ordinal dump)
- **Engine executor:** peel pure helpers into
`executor/{browser-probe,requeue-loop,pseudo-pause,workflow-step-failures}.ts`
- **Engine heartbeat:** peel system prompts/procedures into
`agent-heartbeat-prompts.ts`
- **Ratchet:** one-time baseline truth-up + ratchet-down for touched
files

### Deferred to follow-up PRs (plan U5, U7–U9 + remaining waves)
- Self-healing folder split
- Further remaining-ops domain peels
- Dashboard `legacy.ts` / routes / UI monofiles
- CLI extension + TUI peels

## Test plan

- [x] `pnpm --filter @fusion/core exec tsc --noEmit`
- [x] `pnpm --filter @fusion/engine exec tsc --noEmit`
- [x] Focused vitest: `detect-pseudo-pause`,
`executor-browser-verification`, `clear-terminal-workflow-step-failures`
- [x] `node scripts/check-file-line-count.mjs` clean against updated
baseline
- [ ] CI merge gate (lint/typecheck/build/gate)
- [ ] Browser smoke: N/A for this PR (no dashboard UI route changes)

## Residual Review Findings

None. Review autofix applied dual-home wiring for
`clearTerminalWorkflowStepFailures` only.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added configurable heartbeat procedures for task and no-task scenarios
(including patrol-aware rendering).
* Improved agent-browser availability verification with clearer
availability/status reporting.
  * Added detection for pseudo-pauses and review-handoff requests.
* Expanded core configuration/contract options for
execution/UI/localization, merges, merge queues, and workflow steps.
* **Bug Fixes**
* Improved handling of transient execute-requeue and workflow-step
retry/cleanup behavior, including better Windows path support.
  * Preserved existing public interfaces during internal restructuring.
* **Documentation**
  * Added a multi-phase roadmap for future package reorganization.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 13:34:30 -07:00
gsxdsm
85f8b1f909 feat: shared Postgres multi-node — retire mesh data-plane replication (#2130)
## Summary

- Treat **shared PostgreSQL** (`DATABASE_URL`) as the multi-node durable
data plane; mesh HTTP is membership + optional auth, not task/settings
replication.
- **Peer exchange**: under Postgres backend mode, write queue is
**topology/auth-only**; non-topology pending rows fail rather than
replaying multi-leader task/settings payloads.
- **Mesh routes**: task-ID reserve/commit/abort always hit local shared
allocator rows (ignore remote `coordinatorNodeId`); mesh sync ignores
settings and only exchanges `authMaterial`.
- **Docs**: rewrite multi-project runbook, shared cluster protocol, and
architecture mesh sections for shared-Postgres + claims/leases.

## Context

Follows the SQLite→Postgres cutover. Multiple Fusion nodes can share one
external Postgres while keeping **per-node execution** (worktrees,
processes, claims via `central.task_claims`). Explicit non-goals remain:
scheduler failover and live process migration.

Plan:
`docs/plans/2026-07-15-001-refactor-mesh-shared-postgres-multinode-plan.md`

## Test plan

- [x] `pnpm --filter @fusion/engine exec vitest run
src/__tests__/peer-exchange-service.test.ts`
- [x] `pnpm --filter @fusion/dashboard exec vitest run
src/__tests__/mesh-routes.test.ts`
- [x] `pnpm --filter @fusion/core exec vitest run
src/__tests__/shared-mesh-state.test.ts`
- [ ] CI gate (lint/typecheck/build/gate)
- [ ] Manual (optional): two processes, same `DATABASE_URL`, create task
on A visible on B; settings change without mesh settings sync; claim
exclusivity

## Operator note

Multi-node shared board requires **external** `DATABASE_URL` on every
node. Default embedded Postgres is still single-host.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Improved multi-node deployments using shared PostgreSQL as the durable
source of execution state.
* Task ID reservation/commit/abort now run locally (no remote
coordinator forwarding).
* Mesh syncing now prioritizes topology visibility and authentication
material; settings replication is disabled in shared-Postgres mode.
* **Bug Fixes**
* Prevented task/settings replication over mesh HTTP in shared-Postgres
deployments.
* Refined lease ownership, recovery, and reconciliation to converge via
shared-database primitives.
* **Documentation**
* Updated architecture and shared-mesh protocol guidance, including
multi-node setup and lease/task-ID allocation behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 13:32:33 -07:00
gsxdsm
f6e43d7232 fix: reclaim merger-ai autostashes and stop dropping untracked work
merger-ai's local-checkout sync stashed under fusion-ai-merge-sync-<taskId>,
a label none of merger.ts's reclamation machinery matches — every path keys
off the fusion-merger-autostash: prefix. Those entries were never classified,
never subsumed-dropped, never age-swept, and never surfaced as orphans holding
work, so they accumulated indefinitely: six entries dating back a month were
found on one working tree, and their age made real lost work indistinguishable
from litter. merger-ai now labels through buildAutostashLabel, and the legacy
prefix stays recognized so already-leaked entries are reclaimed rather than
stranded in developers' stash lists.

Routing them into that machinery first required fixing what it does with
untracked files. A stash created with --include-untracked keeps them in a
third parent (<sha>^3) that git stash show omits, so an untracked-only stash
read as empty — and all three copies of the liveness check treated empty as
"subsumed, safe to drop". Every leaked ai-sync stash carried untracked files,
so the fix would otherwise have destroyed the work it was meant to reclaim.
Liveness now resolves through one authority, classifyStashContent, which reads
both sides, diffs untracked paths against <sha>^3 rather than the stash commit
(whose tree never contained them), and treats unreadable state as unknown and
therefore undroppable.

Age-based sweeping is left alone: it drops by timestamp without consulting
content, which is deliberate bounded retention and the backstop against this
same accumulation, not a safety gap.

Regression test uses real git — the defect lives in git's stash object model,
so a mocked git can neither express nor catch it — and asserts the invariant
across tracked-only, untracked-only, and mixed stashes in both live and
subsumed states. The mixed shape (tracked subsumed, untracked live) is the one
that silently lost work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:22:22 -07:00
gsxdsm
5e5fa9a2be fix: auto-approve plans whose approval predates the prompt-hygiene injection
An operator was re-asked to approve a plan they had already approved and
that had not changed.

POST /tasks/:id/approve-plan fingerprints the on-disk PROMPT.md, so a plan
approved before the `## Original Description` hygiene injection
(applyOriginalDescription) shipped carries a hash over PRE-injection
content. On the task's next pass the injection rewrites PROMPT.md, the
fingerprint moves, and FN-7569's idempotency short-circuit misses — so the
manual gate re-parks an unchanged, already-approved plan.

finalizeApprovedTask now also compares the recorded fingerprint against the
as-read (pre-injection) content. This does not weaken the gate: `written`
diverges from `writtenInput` only via that injection, so both arms hash
bytes the operator actually approved — only the representation differs. A
genuinely changed plan matches neither arm and still parks.

On a legacy match the stored fingerprint is migrated forward, so the
reconciliation is one-time per task rather than a comparison carried
forever. The migration is a direct updateTask — the taskUpdates batch is
flushed well before this gate runs.

Covers both finalizeApprovedTask callers (direct + recoverApprovedTask),
asserts the changed-plan safety edge still parks, and asserts no redundant
fingerprint write when the approval is already post-hygiene.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:17:24 -07:00
gsxdsm
3b938887c9 test: fix FN-7569 plan-approval fixtures to model approved on-disk PROMPT.md
The recoverApprovedTask idempotency test failed deterministically, and its
siblings passed for the wrong reason. Both traced to the same stale fixture,
not a product defect.

finalizeApprovedTask injects `## Original Description` into PROMPT.md
(applyOriginalDescription) BEFORE computing the approval fingerprint, and
POST /tasks/:id/approve-plan fingerprints the on-disk file — so the
fingerprint an approval records is always over post-injection content. The
fixtures wrote RAW planner text and fingerprinted that, modelling a state
approve-plan can never produce: the injection then rewrote the content, the
fingerprint moved, and the short-circuit looked broken.

Verified the product is correct: the injection is idempotent, so the real
approve -> recover round-trip fingerprint matches (checked end to end).

- recoverApprovedTask test: write and fingerprint the approved on-disk
  content. It now exercises the real short-circuit — the run logs "plan
  unchanged since prior approval" then "recovered and moved to todo",
  where before it logged "awaiting manual approval".
- same-plan test: it only passed because the injection's rewrite ENOENT'd
  (no task dir), the failure was swallowed, and `written` stayed raw — so
  the fingerprint matched by accident. Feed it the approved content so the
  injection is a genuine no-op and the assertion means something.

Fixtures derive from applyOriginalDescription rather than hard-coding
post-injection text, so they keep meaning "the content the operator
approved" if the hygiene injection changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:10:17 -07:00
gsxdsm
d48537b7a5 docs: correct FN-7779 changeset dev field to the shipped ACP path
The dev field described the pre-ACP adapter: stderr capture, NDJSON error
event bridging, and subprocess exit-code inspection. That implementation
was replaced by the native ACP transport rewrite (grok agent stdio) on
2026-07-11, which carries the FN-7779 invariant forward through onText
diagnostics rather than stderr scraping.

The user-facing summary was already accurate; only the developer-facing
mechanism was stale. Describe the paths that actually ship.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:07:59 -07:00
gsxdsm
2b8df56cb8 fix: escalate reviewer provider errors instead of looping on them
A rate-limited reviewer filled a task's Chat tab with 14 identical
"Reviewer using model: ..." markers and no review text, hammering an
already-limited provider.

Root cause: the reviewer was the only AI lane that never classified
provider errors, so a 429 became an UNAVAILABLE verdict. With no
validator fallback configured the fallback ladder re-ran the SAME model
instantly, and fn_review_step answered with "code review remains
blocking; retry once" — bounding the loop with prompt text rather than
code. The tool's catch-all also swallowed the error into tool output, so
withRateLimitRetry, UsageLimitPauser and RetryStormError never fired.

- reviewer: throw ReviewerProviderError for usage-limit/transient errors
  instead of laundering them into UNAVAILABLE, and never spend the
  fallback budget (which bounds bad reviews) on an outage.
- reviewer: absorb flaky-network blips in-lane via withRetry with
  jittered backoff; rate limits still escalate immediately.
- executor: re-raise the fatal after the prompt via
  throwDeferredReviewerFatal — pi-agent-core converts tool throws into
  tool_error results, so a tool cannot throw out of session.prompt().
- executor: give code review a real MAX_CODE_REVIEW_UNAVAILABLE_RETRIES
  counter, mirroring the plan/spec limiter.
- reviewer: dedupe the model marker on text, so same-model retries stay
  silent while a genuine model switch still emits.

Also fixes the run-on rendering: AgentLogType gains `status` for complete
engine messages. `text` means "streamed delta" and is re-glued with
join(""), which is why N standalone markers rendered as one string. The
split is at the type, not a separator — a separator would reintroduce the
FN-5787/5789/5803 streamed-spacing regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:54:16 -07:00