Commit Graph

1080 Commits

Author SHA1 Message Date
gsxdsm
3badc244a7 U12 part 2: bind the three U5 reconciliation guards — USER-VISIBLE (and one path that couldn't run under PostgreSQL at all) (#2512)
## U12 part 2 — the three U5 reconciliation guards now actually fire

USER-VISIBLE. Taken on standing authority; here is exactly what changed
for operators.

All three read the RAW `experimentalFeatures.workflowColumns` key via
`store.workflowColumnsFlagOn()`. Nothing in production writes it, so all
three have been inert since the workflow-columns cutover.

| Guard | Before (every real project) | After |
|---|---|---|
| Workflow edit removing an **occupied** column | Save succeeded; cards
left in a column the workflow no longer declares | Save fails with
`OccupiedColumnsError` unless `rehomeTo` is supplied |
| Workflow **delete** | Occupant capture returned `[]`; cards sat in the
deleted workflow's columns until the next engine start | Cards move to
the default workflow's entry column as part of the delete |
| Workflow **switch** | Never reconciled; the `reconciliation` field in
the declared return type was never populated | Card in an undeclared
column moves to the resolved target; a declared column is preserved |

Both consumers already handle the new outcomes and needed no change:
`register-workflow-routes.ts` maps `OccupiedColumnsError` to a
structured 409 carrying per-column occupant counts, and
`fn_workflow_update` returns a retryable structured result. The
dashboard editor's `rehomeTo` retry flow becomes reachable for the first
time. I only updated two stale "flag-ON" comments there — that code was
correct all along and simply never fired.

### What an operator actually sees (USER-VISIBLE — read this bit)

Four changes to what the board and the API do. Nothing here is silent.

1. **Editing a workflow to remove a column that has cards in it now
FAILS.** Previously the save succeeded and the cards were left in a
column their workflow no longer declared. The dashboard shows the
existing 409 with per-column occupant counts and prompts for a re-home
target; retrying with `rehomeTo` moves the cards and saves. Removing an
EMPTY column is unaffected.
2. **Deleting a workflow moves its cards immediately** to the default
workflow's entry column, instead of leaving them until the next engine
start.
3. **Switching a task's workflow moves the card** when the new workflow
does not declare its current column. A card whose column IS declared
stays exactly where it is. The API response now carries the
`reconciliation` summary it always promised.
4. **A switch whose re-home would be REJECTED is now refused before
anything is written.** If the destination column is at its WIP limit,
the switch fails with a structured 409 (`workflow-switch-rehome-failed`)
naming the task, both columns and the reason — and **nothing changes**:
the task keeps its current workflow AND its current column. Retry after
making room. Previously this combination committed the selection and
then silently reported a move that never happened, leaving selection and
column disagreeing.

**Can a torn card still happen? Yes, in one narrow case, and here is how
you recover.** If the destination fills in the window between the
pre-flight and the move, the selection is already committed and the card
ends up in a column its new workflow does not declare. That case is not
silent: it writes a `task:workflow-switch-torn` run-audit row, and the
error carries `selectionCommitted: true` with both columns. Recovery:
make room in the destination and move the card there, or switch the task
back — and if neither happens, the R7 startup sweep
`reconcileUndeclaredTaskColumns` re-homes it on the next engine start.
The card is never lost; it is visible in a lane the board may not draw
until one of those runs.

The one thing to watch after merge: (1) converts a previously-silent
success into a visible failure, so an operator mid-edit on a busy
workflow will start seeing a 409 they never saw before. That is the
point — the alternative was stranding their cards — but it is the change
most likely to generate a "this used to work" report.

### The thing that made this more than a gate removal

Un-gating the switch guard surfaced that
`selectTaskWorkflowAndReconcileImpl` read the task through
`store.readTaskFromDb` — the **synchronous SQLite** reader, which throws
under PostgreSQL:

```
TaskStore.db: SQLite Database is not available in backend mode
```

The flag returned before that line, so the gate was hiding a path that
**could not execute at all in the production backend**, not merely a
disabled feature. Ported to the async `readTaskRow`. Found by the new
tests, not by reading the code.

### Review round 2 (both findings real, both fixed)

**Torn write with no alarm — fixed by ORDERING, not by a louder
message.** My first attempt only made the error loud, which left the
torn state intact. The real fix is that the deterministic rejection
cause (destination at its WIP limit) is now checked BEFORE
`selectTaskWorkflow` commits, by resolving the target IR straight from
`workflowId` instead of through the task's selection. Nothing commits on
that path.

For the residual race the failure is loud AND recorded: `rehomeOccupant`
now returns `{ moved, error? }` (additive; sweep callers ignore it), the
switch writes a `task:workflow-switch-torn` run-audit row, and throws
`WorkflowSwitchRehomeFailedError` with `committed: true`. Consumers
translate it: the dashboard route returns a structured 409 with
`selectionCommitted`, and `fn_task_set_workflow` returns the same fields
— no more generic "something went wrong".

**Fabricated column for a deleted task.** My first fix fell back to
`fromColumn` when the final read found no row, so a task soft-deleted
mid-switch was reported as having its old column *preserved*. Absent now
reads as absent (the optional `reconciliation` is omitted). Extracted as
the pure `buildSwitchReconciliation` seam because the window is not
reachable through the public call — `selectTaskWorkflow` rejects an
already-deleted task up front — so it is a genuine race, and I test the
decision directly rather than asserting it from reading the code.

### Revert-proof, measured

New `workflow-reconciliation-production-shape.pg.test.ts` — 6 cases,
with the flag **never written**, which is the configuration every real
project has. Each flip reverted individually:

- re-gate the edit guard → **2 failures** (OccupiedColumnsError case;
rehomeTo re-home case)
- re-gate the delete capture → **1 failure** (card stays in
`custom-hold`)
- restore the switch early return → **2 failures** (`reconciliation`
undefined; card does not move)
- all three in place → **6/6 green**

Round-2 fixes, also measured:
- restore the `fromColumn` fallback → the "row is gone" case fails
(reports `preserved: true` for a deleted task)
- drop the `!outcome.moved` throw → the capacity-blocked case fails
(resolves instead of raising)
- **move the capacity pre-flight back AFTER the commit → the case fails
on the SELECTION assertion** (expected `WF-002`, received `WF-001`),
i.e. it proves the ordering, not the wording

The pre-existing coverage in `workflow-authoritative-reads.pg.test.ts`
reached the occupied-column guard by **writing the flag ON itself** —
same pattern as the ListView/Board suites in part 1. Its flag write is
removed; it now runs in the production shape.

### Where I nearly got this wrong

My first revert harness was buggy and I briefly concluded the delete
re-home was **redundant** — I had probed the stored column and seen
`triage` with what I thought was the flip reverted. It wasn't.
`workflow-ops.ts` contains two identical `const occupantTaskIds = await
store.listWorkflowOccupantTaskIds(id, false)` lines (field-reconcile
block, delete path), so my first-match edit reverted the wrong one.
Re-run anchored on surrounding context, the delete case fails as
predicted. Recorded in the test header as a caution. I also chased and
**refuted** a scarier hypothesis along the way — that an unrelated
`updateTask` coerces a custom column back to `triage`. It does not; the
column survives.

### Deliberately NOT in this PR

The v1-IR rollback-compat persistence (`downgradeIrToV1IfPure`) on the
workflow UPDATE path. It shared the same `flagOn` variable, which is how
it surfaced: **one flag read was feeding two unrelated decisions, so the
flag has more decision sites than call sites** — my earlier 9-site
inventory undercounted. It chooses the stored *shape* of the graph
rather than gating a guard, so it is a persistence-format change with a
different blast radius. It now reads the flag explicitly, behaviour
unchanged, for a follow-up.

The `moves.ts` group remains U2b's.

### Verification

`pnpm test:gate` (307 + 10 + 71), `pnpm lint`, `pnpm verify:fast` (17
steps), both typechecks green. Full `packages/core` PostgreSQL suite:
**1042 passed, 3 failed** — `central-archive-secrets.test.ts`
(log-prefix assertion) and
`workflow-settings-project-identity.pg.test.ts` (×2, project-id
resolution). I confirmed the identical 3 failures on a stashed clean
tree: pre-existing, unrelated. No Fusion instance booted.


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

## Summary by CodeRabbit

* **Bug Fixes**
* Workflow edits now prevent removal of occupied columns unless cards
are moved to a specified destination.
* Cards are automatically re-homed when workflows are deleted or
switched.
* Workflow switches now check destination capacity before committing and
provide clear conflict details when re-homing fails.
* Reconciliation results now indicate whether cards were moved or
preserved.


<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:52:46 -07:00
dependabot[bot]
74d6513fae chore(deps): bump actions/upload-artifact from 4 to 7 (#2444)
Bumps
[actions/upload-artifact](https://github.com/actions/upload-artifact)
from 4 to 7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/upload-artifact/releases">actions/upload-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>v7 What's new</h2>
<h3>Direct Uploads</h3>
<p>Adds support for uploading single files directly (unzipped). Callers
can set the new <code>archive</code> parameter to <code>false</code> to
skip zipping the file during upload. Right now, we only support single
files. The action will fail if the glob passed resolves to multiple
files. The <code>name</code> parameter is also ignored with this
setting. Instead, the name of the artifact will be the name of the
uploaded file.</p>
<h3>ESM</h3>
<p>To support new versions of the <code>@actions/*</code> packages,
we've upgraded the package to ESM.</p>
<h2>What's Changed</h2>
<ul>
<li>Add proxy integration test by <a
href="https://github.com/Link"><code>@​Link</code></a>- in <a
href="https://redirect.github.com/actions/upload-artifact/pull/754">actions/upload-artifact#754</a></li>
<li>Upgrade the module to ESM and bump dependencies by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/762">actions/upload-artifact#762</a></li>
<li>Support direct file uploads by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/764">actions/upload-artifact#764</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Link"><code>@​Link</code></a>- made
their first contribution in <a
href="https://redirect.github.com/actions/upload-artifact/pull/754">actions/upload-artifact#754</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v6...v7.0.0">https://github.com/actions/upload-artifact/compare/v6...v7.0.0</a></p>
<h2>v6.0.0</h2>
<h2>v6 - What's new</h2>
<blockquote>
<p>[!IMPORTANT]
actions/upload-artifact@v6 now runs on Node.js 24 (<code>runs.using:
node24</code>) and requires a minimum Actions Runner version of 2.327.1.
If you are using self-hosted runners, ensure they are updated before
upgrading.</p>
</blockquote>
<h3>Node.js 24</h3>
<p>This release updates the runtime to Node.js 24. v5 had preliminary
support for Node.js 24, however this action was by default still running
on Node.js 20. Now this action by default will run on Node.js 24.</p>
<h2>What's Changed</h2>
<ul>
<li>Upload Artifact Node 24 support by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/719">actions/upload-artifact#719</a></li>
<li>fix: update <code>@​actions/artifact</code> for Node.js 24 punycode
deprecation by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/744">actions/upload-artifact#744</a></li>
<li>prepare release v6.0.0 for Node.js 24 support by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/745">actions/upload-artifact#745</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0">https://github.com/actions/upload-artifact/compare/v5.0.0...v6.0.0</a></p>
<h2>v5.0.0</h2>
<h2>What's Changed</h2>
<p><strong>BREAKING CHANGE:</strong> this update supports Node
<code>v24.x</code>. This is not a breaking change per-se but we're
treating it as such.</p>
<ul>
<li>Update README.md by <a
href="https://github.com/GhadimiR"><code>@​GhadimiR</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/681">actions/upload-artifact#681</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/712">actions/upload-artifact#712</a></li>
<li>Readme: spell out the first use of GHES by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/727">actions/upload-artifact#727</a></li>
<li>Update GHES guidance to include reference to Node 20 version by <a
href="https://github.com/patrikpolyak"><code>@​patrikpolyak</code></a>
in <a
href="https://redirect.github.com/actions/upload-artifact/pull/725">actions/upload-artifact#725</a></li>
<li>Bump <code>@actions/artifact</code> to <code>v4.0.0</code></li>
<li>Prepare <code>v5.0.0</code> by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/734">actions/upload-artifact#734</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="043fb46d1a"><code>043fb46</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/797">#797</a>
from actions/yacaovsnc/update-dependency</li>
<li><a
href="634250c138"><code>634250c</code></a>
Include changes in typespec/ts-http-runtime 0.3.5</li>
<li><a
href="e454baaac2"><code>e454baa</code></a>
Readme: bump all the example versions to v7 (<a
href="https://redirect.github.com/actions/upload-artifact/issues/796">#796</a>)</li>
<li><a
href="74fad66b98"><code>74fad66</code></a>
Update the readme with direct upload details (<a
href="https://redirect.github.com/actions/upload-artifact/issues/795">#795</a>)</li>
<li><a
href="bbbca2ddaa"><code>bbbca2d</code></a>
Support direct file uploads (<a
href="https://redirect.github.com/actions/upload-artifact/issues/764">#764</a>)</li>
<li><a
href="589182c5a4"><code>589182c</code></a>
Upgrade the module to ESM and bump dependencies (<a
href="https://redirect.github.com/actions/upload-artifact/issues/762">#762</a>)</li>
<li><a
href="47309c993a"><code>47309c9</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/754">#754</a>
from actions/Link-/add-proxy-integration-tests</li>
<li><a
href="02a8460834"><code>02a8460</code></a>
Add proxy integration test</li>
<li><a
href="b7c566a772"><code>b7c566a</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/upload-artifact/issues/745">#745</a>
from actions/upload-artifact-v6-release</li>
<li><a
href="e516bc8500"><code>e516bc8</code></a>
docs: correct description of Node.js 24 support in README</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/upload-artifact/compare/v4...v7">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/upload-artifact&package-manager=github_actions&previous-version=4&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com>
2026-07-27 19:06:09 -07:00
gsxdsm
4158cf1ab7 Phase A: workflow-owned lifecycle foundation (U1, U2, U3) (#2467)
Phase A (Foundation) of
`docs/plans/2026-07-26-001-refactor-workflow-owned-lifecycle-plan.md`.
Three units, one commit each. No operator-visible behavior change.

## U1 — Lifecycle-column resolution seam

`resolveLifecycleColumns(ir)` returns `{ intake, hold, wip, review,
complete, archived }` — the first column carrying each trait,
`undefined` for a role no column carries.
`resolveTaskLifecycleColumns(store, taskId, cache?)` is the store-aware
form; the cache is caller-owned so a sweep reads one IR per workflow
rather than one per card.

A v1/column-less IR resolves to `undefined` for the **whole struct**
rather than a struct of undefined roles. A caller must be able to
distinguish "this workflow declares no hold column" (a real shape to
honor) from "no column vocabulary at all" (skip and log) — only the
second licenses conservative fallback.

Nothing consumes the seam yet; Phases B–D convert the ~207 hardcoded
column literals onto it.

## U2 — Delete the pre-cutover parity machinery (delete-only)

**`workflow-columns-settings.ts`** — `isWorkflowColumnsEnabled` had the
body `return true`. Six live call sites branched on it, so every
flag-OFF arm was dead code that read as a supported configuration.
Deleted; surviving side inlined at self-healing's transitionPending
sweep, the scheduler's per-column capacity diagnostic, merge-trait's
policy resolver, the board-workflows payload, two task-workflow routes,
and the CLI TUI's column enrichment.

**`workflow-parity.ts`** — asserted the default workflow's adjacency
*equals* the legacy `VALID_TRANSITIONS`. U11 deliberately breaks that
equality by merging Todo into Planning, so this is not a stale assertion
to update; it is a contract against the target state. Its emitter
(`workflow-parity-observer.ts`) is already a tombstone, so
`getWorkflowParitySummary` and `computeWorkflowColumnsGraduationReport`
aggregated run-audit rows nothing writes and had no caller outside
`TaskStore`. Both store methods go with it.

`flagEnabled` stays on the board-workflows **wire** as a constant `true`
— shipped dashboard clients still branch on it, and changing the
response shape is not a deletion. U10 retires the field once no client
reads it.

The `legacy-tombstones` ratchet is extended to both files plus seven
symbols, each with the reason it is gone.

### ⚠️ Finding: the third listed deletion was NOT dead

The plan also lists "the flag-off inline move path" in
`task-store/moves.ts`. It is **not** deleted, per U2's execution note
("any behavior change found while removing a branch means the branch was
not dead").

That path is gated on `isWorkflowColumnsCompatibilityFlagEnabled`
(`store.ts:38`) — a **different** function from the always-true public
helper. It reads the raw `experimentalFeatures.workflowColumns` setting,
which nothing in production sets (`settings-schema.ts:396` — "no default
flags are emitted"; zero non-test writers; the operator's own
`~/.fusion/settings.json` has no such key). So `useWorkflow` is false
for effectively every real project: the flag-OFF inline side effects are
the **live** default move path and the flag-ON `default-workflow-hooks`
path is the dead one. The code says so itself at `moves.ts:638`.

Deleting that branch would swap every project onto an untravelled code
path — a behavior change, not a deletion.

**Carry this into Phases B and C, stated plainly so the plan's error is
not repeated:**

> **The inline move path in `moves.ts` is LIVE.
`default-workflow-hooks.ts` (the trait-hook path) is DEAD.** KTD-6
asserted the inverse. Until the convergence unit lands, **nothing may
assume trait hooks run** — a guard, sweep, or subscriber written against
`applyDefaultWorkflowMoveEffects` would never fire in production and
would still pass its tests.

Convergence is **not** attempted here. It is its own unit (Phase A2)
with a proper equivalence proof, per operator decision.

### U3's emit point is on the LIVE path — the seam is not born dead

Worth stating explicitly because it is the failure mode that would make
every later subscriber silently never fire: the `TaskTransitioned` emit
is **not** inside the `if (useWorkflow)` branch. That block closes at
`moves.ts:1212`; the emit sits at `:1214`, beside the existing
`store.emit("task:moved", …)`, on the unconditional post-commit path. It
therefore fires on **both** the live inline path and the dead hooks
path, and the convergence unit inherits the obligation to keep it firing
on whichever path survives — same events, same order, same payloads.

The graph-side emitters (`NodeEntered`, `RunSuspended`) carry the same
risk from a different direction: the bus refuses an invalid payload
*silently* by design, so an emitter regression would stop the event with
no test failure. They are asserted end-to-end through the real bus —
"did a subscriber actually receive it", not "was emit called" — because
a spy passes on a refused payload. The `moveTaskInternalImpl` emit does
**not** yet have that end-to-end assertion against a real store move;
that proof belongs to the convergence unit, which has to build the
both-paths fixture anyway.

## U3 — Post-commit event seam with a transactional outbox

**The bus is not a queue, not a transaction participant, and not a
delivery guarantee.** Durable follow-on work uses the transactional
outbox — a `workflow_work_items` row written *inside* the transition
transaction (the shape `createCompletionHandoffWorkflowWork` already
uses). "Emit after commit, let a subscriber enqueue the work" has a
crash window where a process dies between commit and subscriber, leaving
no event *and* no work-item row, so required work is skipped permanently
with nothing to recover from. Post-commit subscribers therefore carry
only losable reactions.

Emission is consequently lossy and isolated by design: a throwing or
rejecting subscriber is caught and logged, cannot roll back the
transition, and cannot stop the others. Deliveries append to one serial
chain, so two transitions on a task deliver in commit order.

The ids/outcomes-only rule is **mechanised, not documented** —
run-audit's equivalent lives only in prose and has been violated
repeatedly. A payload carrying an object body or a prose string is
refused at the emit boundary and never reaches a subscriber or log sink.
It degrades rather than throws: the emitter is post-commit, so a shape
bug must not become a lifecycle failure.

Emit points: `TaskTransitioned` from the single post-commit point in
`moveTaskInternalImpl`; `NodeEntered` and `RunSuspended` from the graph
column boundary, the latter *after* the durable continuation is
persisted so an observed suspension implies a resumable run.

`registerWorkflowEventSubscribers` (engine) is empty on purpose —
U7/U8/U10 move real reactions onto it, each with the characterization
test proving the reaction was non-authoritative first.

## Verification

- `pnpm test:gate` — green (2/10, 16/299, 1/71).
- `pnpm lint`, `pnpm build`, `tsc --noEmit` on core and engine — green.
- U1: 20 tests in `workflow-lifecycle-traits.test.ts`, including the
fully-renamed-workflow case (fails if the resolver falls back to a
literal) and a shared-cache read-count assertion.
- U2: `legacy-tombstones.test.ts` green with the extended ratchet;
`board-workflows`, `merge-trait`, `workflow-graph-executor-parity`, and
move-hook suites green with no expectation edits.
- U3: 20 bus-invariant unit tests (isolation, ordering, the allowed-key
and required-key halves of the ids-only rule, lossiness) plus 3
end-to-end emitter-delivery tests; 5 outbox tests against a **real
PostgreSQL** work-item table (crash survival, rollback, at-least-once
redelivery on lease expiry, idempotent handler → one effect,
dropped-subscriber vs. durable work). A hand-written fake of the lease
predicate would only prove the fake redelivers.

**Not verified:** the `moveTaskInternalImpl` emit is confirmed on the
unconditional post-commit path by structure and by the surrounding
tests, but is *not* yet asserted end-to-end against a real store move on
both flag settings — that is Phase A2's fixture. The engine subscriber
registry ships empty by design, so no production subscriber exercises
the bus end-to-end yet. `settings-defaults.test.ts` has one pre-existing
failure on `main` (a logger-prefix mismatch in the
`mergeIntegrationWorktree=cwd-main` warning) — confirmed present on a
clean tree, unrelated to this branch.

🤖 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**
* Workflow lifecycle columns are now derived from workflow definitions,
supporting renamed and custom workflows.
* Added post-commit lifecycle events for task transitions, node entry,
and run suspend/resume with validated payloads.
* Follow-on processing for lifecycle emissions is now more robust
(rollback-safe, at-least-once delivery, idempotent handling).
* **Bug Fixes**
* Workflow board responses, task enrichment, and promotion no longer
depend on workflow-columns feature-flag gating.
  * Subscriber failures no longer impact committed workflow transitions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-27 13:30:13 -07:00
gsxdsm
8b039a543e fix(desktop): advance Pi runtime pin to 0.82.1 for packaging PR lane (#2465)
## Summary
- Advance the matched Pi runtime pin (`pi-ai`, `pi-coding-agent`,
`pi-agent-core`, `pi-tui`) from **0.82.0 → 0.82.1** so
electron-builder's production-dependency walk accepts `pi-agent-core`'s
`pi-ai@^0.82.1` requirement.
- Fixes the Desktop packaging PR-lane failure:
`Production dependency @earendil-works/pi-ai not found for package
@earendil-works/pi-agent-core` (required `^0.82.1`).
- Keep the workspace override guard; update pin-policy fixtures and CLI
package-config expectations.
- Tighten the advisory packaging step-order test so it asserts against
the real `electron-builder --dir` step (not a missing release-only step
name that previously passed via `indexOf === -1`).
- Run `pnpm dedupe` so the packaging lane's lockfile dedupe
early-warning is clean.

## Context
#2439 pinned the full Pi closure at 0.82.0 and made recent main-based
packaging runs green. This advances to the current upstream patch so
deploy + electron-builder stay aligned with `pi-agent-core@0.82.1`'s
declared dependency range.

## Test plan
- [x] `node scripts/check-pi-versions-pinned.mjs`
- [x] `node --test scripts/__tests__/check-pi-versions-pinned.test.mjs`
- [x] `pnpm --filter @runfusion/fusion exec vitest run
src/__tests__/package-config.test.ts`
- [x] `pnpm --filter @fusion/desktop exec vitest run
src/__tests__/release-workflow.test.ts`
- [x] `pnpm dedupe --check`
- [ ] GitHub: Desktop packaging (should run full packaging walk —
lockfile/package.json touched)
- [ ] GitHub: PR Checks (Lint, Typecheck, Build, Gate)
2026-07-26 23:47:49 -07:00
gsxdsm
3f33cb000f feat: per-origin workflow selection + feedback-derived refinement titles
Two task origins had no workflow picker in front of the operator and always
inherited the project default: `fn task create` (CLI + the `fn_task_create`
agent tool) and refinement tasks. Add a Project General setting for each, where
blank/unset means "Selected workflow" (the operator's current Board lane,
falling back to the project default) and a concrete id pins that origin.

Because the Board lane lives in browser localStorage, non-browser callers could
not resolve "Selected workflow" at all. `boardSelectedWorkflowId` mirrors the
lane into project settings so they can. Note this makes the mirrored lane
project-scoped: two operators on one project share it, last switch wins. The
Board never reads it back, so the only effect is which workflow a newly created
task inherits.

Resolution is `TaskStore.resolveOriginWorkflowOverrideId(origin)`: pinned
setting -> mirrored lane -> `undefined` to inherit each caller's existing
default-workflow path unchanged. A deleted or fragment id degrades to inherit
rather than throwing, so a stale settings value can never break task creation.
An explicit `workflow_id` argument to `fn_task_create` still wins.

Separately, a refinement is now titled by the operator's own feedback via the
shared `deriveFallbackTaskTitle`, not `Refinement: <parent title>`. Ten
refinements of one task previously rendered ten identical titles, so the board
could not tell them apart while the text saying what each one asked for sat in
the description. Provenance moves to a `Refines <id>` card chip alongside the
existing detail-view parent link and dependency edge.

Verified: merge gate (299 tests), lint, full build, and typecheck for core, CLI,
and dashboard all pass. New coverage: origin resolution across both origins and
the full precedence ladder, the two settings pickers, the board-lane mirror,
refinement titling (including sibling distinctness), and the card chip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 20:33:25 -07:00
gsxdsm
ab87d0d803 fix(api): return 404 for missing tasks, and make task deletions attributable
Three related fixes, all originating from a `[api:error] Request failed`
log line showing a 500 on `GET /api/tasks/FN-8610/runtime-fallback`.

1. Missing/deleted tasks now return 404 instead of 500.
   `getTaskImpl` signalled a miss with a bare `Error`, and route catches
   only mapped errno `ENOENT` to 404 — a leftover from the file-backed
   storage era. In Postgres mode nothing sets an errno code, so every
   unknown/missing/soft-deleted/wrong-project read returned 500. Adds a
   typed `TaskNotFoundError` (message byte-identical) plus a shared
   `task-lookup-error` mapper applied across the task, session-diff,
   git/GitHub, workflow and file-workspace route registrars. The same
   bare throw existed on both archive-lifecycle delete paths, so
   `DELETE /tasks/:id` was affected too.

2. 5xx logs now carry the origin stack.
   `rethrowAsApiError` constructed a fresh `ApiError` from the message
   and discarded the original, so the `FNXC:ApiErrorDiagnostics`
   contract logged the rethrow site rather than the throw site — the
   reported log entry had no stack at all. Threads `cause` through the
   error factories and walks the chain (bounded, cycle-guarded).

3. Task deletions are attributable, and non-operator deletes notify.
   `task:deleted` audit rows recorded `agentId: "system"` for every HTTP
   delete, making an operator click indistinguishable from a script or
   an agent; the calling agent's task id was accepted by the store and
   then never persisted. Adds a `callerKind` union recorded in audit
   metadata, tags every delete call site, and stamps a self-reported
   `x-fusion-client` header from the dashboard client. When the caller
   is `agent-tool` or `api-unattributed`, a best-effort notice is sent
   to the operator mailbox; operator and engine deletes stay silent.

`x-fusion-client` is attribution, not authentication — anything can send
it. No delete-blocking, gating or permission logic is added here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 13:15:36 -07:00
gsxdsm
106c61e6ee fix(agent-tools): close the fn_delegate_task Deny bypass and the store's window clamp
Follow-up to 13a2b2a9d, from a multi-agent review of that commit. Three of its
claims did not hold.

1. fn_delegate_task bypassed the gate entirely (P0). It reaches the same
   createAgentTask primitive, was registered unconditionally in both session
   lanes, and validated only that the TARGET agent is non-ephemeral — never the
   caller. Under Deny an ephemeral worker could enumerate agents and delegate
   unlimited tasks. It is now withheld under Deny, and also under
   upon_validation: delegation has no proposal channel, so leaving it available
   would launder a create past the operator review that policy requires.

2. The widened dedupe window was capped at 5 minutes. The store query in
   branch-and-pr-entities.ts carried its own independent `?? 60_000` /
   `min(300_000, …)` pair, so widening only duplicate-guard.ts under-delivered
   and made the new ceiling unreachable. Both sites now share
   FINGERPRINT_WINDOW_DEFAULT_MS / FINGERPRINT_WINDOW_MAX_MS.

3. The pi-extension gate does not fire at all. pi's ExtensionContext carries no
   agentId — the read is a speculative cast and only tests supply one, so every
   real call short-circuits as a human caller. The fail-closed direction is kept
   for the day an identity signal exists, but the limitation is now documented
   instead of implied to be enforcement.

Also: the session prompt now states when creation is disabled and names
fn_task_log as the fallback (the base prompt still taught fn_task_create, which
is the same instruction/capability mismatch that fed the retry storm);
suppression emits an `agent:task-create-withheld` run-audit event; and the two
source-text ratchet tests are replaced with behavioral assertions on the tool
list the executor actually hands the model — verified to fail when the guard is
broken, which the string assertions did not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 23:44:56 -07:00
gsxdsm
13a2b2a9da fix(agent-tools): hide fn_task_create under Deny and widen the dedupe window
Operator report: with project policy "Ephemeral agent follow-up tasks = Deny",
an executing agent filed ten follow-up tasks — five parallel fn_task_create
calls it reported as timed out, then five sequential retries.

Two defects:

1. Deny was advisory. fn_task_create was registered for every session and only
   refused inside execute(), so the model still saw the tool, planned around it,
   and retried it. The pi extension's isEphemeralCallerAgent also failed OPEN
   whenever the caller id did not resolve to an agent row — which is the normal
   shape of an ephemeral task-worker — so on that lane Deny was a no-op.

2. The deterministic content-fingerprint duplicate window was 60s, which only
   covered concurrent in-flight creates. A retry two minutes later saw nothing
   and filed a second task.

Fixes: isAgentTaskCreateToolAvailable() withholds the tool from ephemeral
sessions under Deny in both engine lanes (outer execution session, per-step
workflow session); isEphemeralCallerAgent fails closed on an unresolvable
caller id; the fingerprint window goes 60s -> 10m (clamp ceiling 5m -> 1h).
upon_validation keeps the tool, and permanent-agent and human/chat callers are
unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 23:03:46 -07:00
gsxdsm
99b80ad748 feat(dashboard): add opt-in auto-update and harden restart supervision
Add the `autoUpdateAndRestart` global setting (default off, Settings ->
General next to Release channel). When enabled, the dashboard host installs
available updates on the selected channel by itself and requests the
supervised in-place restart. Supervised hosts only: without a parent to
respawn, installing would leave a running process whose code no longer
matches its own install.

Fix two ways the restart affordance could silently do nothing:

- The supervisor now stamps FUSION_SUPERVISOR_PID and supervision is only
  counted when that pid is the real parent. FUSION_RESTART_SUPERVISED is
  inherited by every process Fusion spawns, so `fn dashboard` launched from
  an agent terminal skipped its own supervisor while still advertising
  restart support -- a restart request then killed it for good.
- Settings and the update banner probe /system/info on mount and treat
  capability as advisory: the button always issues the request and shows the
  server's actual refusal instead of sitting disabled after a failed probe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 22:52:53 -07:00
gsxdsm
2ea5ac206d fix(cli-tests,dashboard): align quiet-mode result spies and voiceInput allowlist
CLI JSON/create success lines write via result() (raw stdout) so quiet mode
cannot drop machine-readable output; capture that seam in research/update/task
tests instead of console.log. Allowlist nested voiceInput settings for the
FN-7505 default-description guard and ship locale keys for Voice Input UI.
2026-07-25 09:02:59 -07:00
gsxdsm
b31bee03a8 FN-8576: add global quiet flag to CLI
Add a global quiet mode that suppresses informational CLI stdout without hiding requested results.

- Parse --quiet/-q and FUSION_QUIET with command and output exemptions.
- Route result output and interactive prompts around the reversible stdout gate.
- Document the flag and cover quiet output, prompts, and argument parsing.

Files changed:
 .changeset/fn-8576-cli-quiet-flag.md               |   7 ++
 docs/cli-reference.md                              |   8 ++
 packages/cli/src/__tests__/bin.test.ts             |  15 +++
 packages/cli/src/__tests__/cli-quiet-mode.test.ts  |  63 +++++++++++++
 .../__tests__/cli-quiet-prompt-surfaces.test.ts    |  33 +++++++
 packages/cli/src/bin.ts                            |  39 ++++++--
 packages/cli/src/commands/experiment-finalize.ts   |   3 +-
 packages/cli/src/commands/git.ts                   |  17 ++--
 packages/cli/src/commands/goals.ts                 |   4 +-
 packages/cli/src/commands/mission.ts               |   8 +-
 packages/cli/src/commands/node.ts                  |   4 +-
 packages/cli/src/commands/onboard.ts               |   4 +-
 packages/cli/src/commands/org-import.ts            |   5 +-
 packages/cli/src/commands/plugin.ts                |  14 ++-
 packages/cli/src/commands/port-prompt.ts           |   4 +-
 packages/cli/src/commands/project.ts               |   6 +-
 packages/cli/src/commands/research.ts              |   3 +-
 packages/cli/src/commands/task.ts                  | 101 +++++++++++---------
 packages/cli/src/commands/update.ts                |   3 +-
 packages/cli/src/commands/workflow.ts              |  13 +--
 packages/cli/src/output.ts                         | 104 +++++++++++++++++++++
 packages/cli/src/project-resolver.ts               |  20 ++--
 22 files changed, 388 insertions(+), 90 deletions(-)

Fusion-Task-Id: FN-8576

Fusion-Task-Lineage: 2493d6d5-bbd9-4fc2-b903-950457ef30b0

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-25 00:54:46 -07:00
gsxdsm
e3dba364d1 FN-8564: update bundled Pi runtime to 0.82.0
Update Fusion's matched Pi dependencies and compatibility coverage for version 0.82.0.

- Pin Pi AI and coding-agent packages to the exact 0.82.0 release pair.
- Refresh provider catalog, supplemental model, auth storage, and Droid thinking coverage.
- Add the published CLI patch changeset.

Files changed:
 .changeset/fn-8564-pi-082.md                       |   7 +
 packages/cli/package.json                          |   4 +-
 packages/cli/src/__tests__/package-config.test.ts  |   2 +-
 packages/core/package.json                         |   2 +-
 packages/dashboard/package.json                    |   2 +-
 ...ister-model-routes-kimi-k3-supplemental.test.ts |   6 +-
 packages/engine/package.json                       |   4 +-
 .../src/__tests__/provider-registration.test.ts    |   4 +-
 packages/engine/src/auth-storage.ts                |  11 +-
 packages/engine/src/pi.ts                          |   6 +
 packages/pi-claude-cli/package.json                |   8 +-
 .../src/thinking-config.ts                         |  10 +-
 pnpm-lock.yaml                                     | 176 +++++++++++----------
 pnpm-workspace.yaml                                |   6 +-
 .../__tests__/check-pi-versions-pinned.test.mjs    |   8 +-
 15 files changed, 142 insertions(+), 114 deletions(-)

Fusion-Task-Id: FN-8564

Fusion-Task-Lineage: 543c5e17-4cb2-446f-9a1c-ec7ec8b8117a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-24 19:06:14 -07:00
gsxdsm
749167cbed fix(engine-controls): surface a paused engine instead of showing it as idle
A paused engine with nothing running derived executorState "idle", so the
footer badge was indistinguishable from a healthy engine waiting for work.
That is exactly the state a pause settles into once in-flight tasks drain:
triage and planning stall while the board keeps moving, with the pause only
visible by opening the Engine Control menu. Pause state now dominates run
state; the adjacent counters still report throughput.

In the CLI TUI, the global `t` (Git view) branch returned before the Utilities
dispatch in the same key handler, so the advertised "[t] Toggle Engine Pause"
was unreachable dead UI. It now yields when Utilities owns input, and because
the shortcut can stop the board, pausing takes a second `t` within 5s while
resuming stays single-press.

Tests assert the invariant across the whole state matrix (both pause flags x
0/1/5 running), not just the zero-running repro, plus the TUI routing, the
two-press pause, single-press resume, and re-arm behavior.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 18:49:19 -07:00
gsxdsm
ab41554980 fix(cli-tests): load task.js once for lock-retry suite under real timers
Full Suite shard 4 timed out runTaskShow lock-exhaustion at the default 5s
budget (run 30096660913): each case re-imported the heavy task.js graph under
fake timers, so cold CI workers spent the whole budget on transform and left
unhandled store.getTask / process.exit races after the timeout.

Import task.js once per cached-store describe under real timers via a mutable
store holder, and enable fake timers only around the backoff body. Exhaustion
cases now finish in ~1ms locally while keeping the shipped entry path.
2026-07-24 08:27:57 -07:00
gsxdsm
e17151cba5 fix(cli-tests): align CLI suite with inbox-mail help, beta versions, and planning-session store
Stale-test drift, no product changes: FN-8424 inbox reply help text and
fn message inbox --user routing, beta-track prerelease suffixes in version
regexes (0.73.0-beta.N), FN-8399 onMigrationProgress in createTaskStoreForBackend,
#2400 workflow-docs heading, and the durable planning-session store mocks for
fn task plan (0412113de/fdd120232).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 22:35:12 -07:00
gsxdsm
0412113de7 fix(dashboard,cli): un-dead-end deleted plan tasks; harden fn task plan per review
Reported bug (screenshot): deleting the task created from a plan left the
session permanently stuck on PLANNING_CREATED_TASK_MISSING — Retry create
replayed the same 409 forever. A linked task absent from the
include-archived scan (task-row authority; a successful scan proves
deletion, not a flaky read) now clears the stale linkage and creates a
fresh task, in both the create-task route and createTaskFromPlanSession;
a still-listed-but-unreadable task keeps failing closed.

Multi-agent review of fdd120232 (correctness/adversarial/reliability):
- P1: CLI planning sessions were memory-only — setAiSessionStore only ran
  in the dashboard server, so --resume could never find a session across
  invocations. New ensureDurablePlanningSessionStore wires the durable
  AiSessionStore over the board store's public asyncLayer in runTaskPlan.
- P1: resume failures now THROW instead of process.exit (fn_task_plan
  runs inside the pi host — an exit killed the whole agent session), and
  a no-question resume requires an explicit refine focus (the provided
  description) so merely resuming never rotates the epoch.
- P2: claim and finalize CAS gained the same expected-epoch WHERE guard
  as reconcile, so a stale-epoch creator can no longer finalize an
  old-epoch task onto a rotated session.
- Side-effect failures (documents, logEntry, validate, reconcile) are now
  logged instead of swallowed; post-insert failures no longer mislabel
  the just-created task alreadyCreated:true; the keep-refining readline
  closes on thrown prompts and a failed refine after creation returns the
  created task id with a resume hint; cross-process generating guard
  added to createTaskFromPlanSession.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 20:19:18 -07:00
gsxdsm
fdd1202328 feat(cli): claim-aware multi-task planning parity for fn task plan
Closes the P1 agent-native gap from the multi-task review: the CLI and
fn_task_plan pi tool created tasks via a raw store.createTask with no
proposalClaimId — no idempotency, no session linkage, and tasks outside
the epoch sequence, so a later dashboard Proceed would duplicate them.

- New shared createTaskFromPlanSession in @fusion/dashboard/planning:
  the agent-surface twin of POST /planning/create-task (epoch-derived
  claim key, claim/finalize/reconcile/release CAS lifecycle with the 30s
  stale-lease takeover, formatPlanningPlanMd task shape, plan/original-
  description documents, validate-on-create, generating guard).
- runTaskPlan creates through it (making the FN-7734 retry wrapper
  genuinely safe), prints the session id, and offers an interactive
  keep-refining loop that creates further tasks from the evolved plan.
- fn task plan --resume <sessionId> / fn_task_plan resumeSessionId reopen
  an existing session — even a validated one whose task exists — and the
  no-question resume regenerates the interview via a refine turn, which
  rotates the creation epoch server-side.

Tests: CLI suite pins claim-aware creation, the continue prompt, and the
resume flow; dashboard suite pins createTaskFromPlanSession idempotent
replay and epoch-aware second creation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 20:01:50 -07:00
gsxdsm
11fab37379 feat(ci): incremental caching for the PR merge-gate jobs (Gate, Build, Typecheck) (#2421)
## Summary

Every PR's blocking checks were dominated by redundant full rebuilds,
not by tests. Measured on recent runs: the Gate job spent ~6 of its ~7.5
min on a cold `pnpm build` (the exact-key dist cache missed on virtually
every PR) for ~45s of actual boot smoke + gate tests; Build ran ~8 min
and Typecheck ~4 min, both fully cold every time. Expected end state
once the warm job has run on main: all four blocking checks in roughly
2–4 min wall-clock.

### Gate job
- New `gate-dist-*` cache namespace with `restore-keys`, additionally
caching `.fusion/cache/plugin-build-cache.json` (build-workspace's
per-package content-hash skip cache) and `packages/cli/dist`. The
always-run `pnpm build` reconciles a near-match restore by content hash
and rebuilds only the packages the PR touched. This is safe *because*
the gate builds after restoring — the shard jobs' "no restore-keys" rule
(FN-4232/FN-4605 stale-dist incidents) still stands there, since they
consume dist without building.
- `FUSION_CLI_FULL_PACKAGE=0` on the gate build: skips the multi-minute
CLI desktop/plugins/DTS packaging tail nothing in the gate consumes
(same shape `pnpm verify:fast` proves locally). Full CLI packaging
coverage stays blocking in the Build job.

### Build job
- Restore-only tap (`actions/cache/restore`) of the same warmed cache.
Restore-only because this job runs FULL CLI packaging (`CI=true`) and
saving that shape would swap the cache's canonical fast-CLI contents out
from under the Gate job. Its distinctive coverage is preserved:
`ensureFullPackageCliPlanned` force-plans the CLI in full mode
regardless of cache state.

### Typecheck job
- Caches per-package tsc incremental buildinfo — self-validating (tsc
hashes every input against it and re-checks whatever changed), so
`restore-keys` is correctness-neutral by construction.
- **Fixes a real incrementality bug:** `tsconfig.json` and
`tsconfig.app.json` in the dashboard both inherited
`${configDir}/dist/.tsbuildinfo` from `tsconfig.base.json`, so the two
typecheck programs clobbered each other's buildinfo and re-checked the
full program every run — incremental typechecking never worked for the
dashboard, in CI or locally. `tsconfig.app.json` now writes
`dist/.tsbuildinfo-app`. Measured: dashboard typecheck 44s cold → 5.6s
warm.

### Warm job (full-suite.yml, push to main)
- New `warm-gate-build-cache` job saves both caches from main on every
push. Caches saved on a PR merge ref are invisible to other PRs, so
without this every PR's *first* run would still build/check cold.

## Guardrails

`ci-workflow.test.ts` pins the coupled invariants so they can't drift
apart silently:
- restore-keys requires the reconciling `pnpm build` after restore,
before boot smoke
- the mtime-defeating seed step stays exact-hit-only
- byte-identical cache path lists between the Gate/Build/warm blocks
(actions/cache versions caches by path list — a drifted list makes
caches mutually invisible)
- Build stays restore-only and must NOT opt out of full CLI packaging
- Typecheck cache shape + the distinct dashboard app buildinfo path

## Notes

- First PR runs after this lands still build cold until the warm job has
run once on main.
- No changeset: CI config + test-only per AGENTS.md.

## Verification

- `ci-workflow.test.ts` + `package-config.test.ts`: 106 tests pass
- Dashboard typecheck run twice locally: 44s cold → 5.6s warm, both
`.tsbuildinfo` and `.tsbuildinfo-app` written, exit 0
- Cache block path/key parity verified programmatically across both
workflow files

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

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

## Summary by CodeRabbit

* **Performance**
* Improved CI build and type-check performance through incremental
caching.
* Added cache warming from the main branch to speed up pull request
checks.
* Enabled faster CLI packaging during gate validation while retaining
full packaging coverage elsewhere.

* **Bug Fixes**
* Prevented dashboard TypeScript build information from being
overwritten, preserving incremental type-checking reliability.

* **Tests**
* Added coverage to verify CI cache behavior, build ordering, cache
paths, and packaging modes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:53:31 -07:00
gsxdsm
e5caea542a FN-8544: gate mission remediation behind autopilot
Keep mission validation report-only until an operator explicitly enables autopilot.

- Gate validator-created remediation features and task dispatch behind mission autopilot.
- Audit attributed status and autopilot transitions atomically across mission stores.
- Expose mission autonomy controls and document the opt-in lifecycle.

Files changed:
 .changeset/fn-8544-mission-autonomy-audit.md       |   7 ++
 docs/missions.md                                   |  10 +-
 packages/cli/src/extension.ts                      |  26 ++++-
 .../__tests__/postgres/mission-store.pg.test.ts    |  27 +++++
 packages/core/src/async-mission-store.ts           |  70 +++++++++++--
 packages/core/src/index.gate.ts                    |   3 +
 packages/core/src/index.ts                         |   3 +
 packages/core/src/mission-store.ts                 | 113 +++++++++++++--------
 packages/core/src/mission-types.ts                 |  22 ++++
 .../dashboard/app/components/MissionManager.tsx    |   1 +
 packages/dashboard/src/mission-routes.ts           |  25 +++--
 .../src/__tests__/agent-mission-tools.test.ts      |  17 +++-
 .../src/__tests__/mission-execution-loop.test.ts   |  21 ++++
 packages/engine/src/agent-heartbeat.ts             |   4 +-
 packages/engine/src/agent-tools.ts                 |  30 +++++-
 packages/engine/src/executor.ts                    |   5 +-
 packages/engine/src/mission-autopilot.ts           |  51 +++++-----
 packages/engine/src/mission-execution-loop.ts      |  49 ++++++---
 packages/engine/src/triage.ts                      |   5 +-
 19 files changed, 377 insertions(+), 112 deletions(-)

Fusion-Task-Id: FN-8544

Fusion-Task-Lineage: 23a69923-5a19-407e-9fe2-8973c166ee9a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-23 15:25:09 -07:00
gsxdsm
0d355f371c FN-8547: add local OpenAI-compatible provider onboarding
Add guided onboarding for OpenAI-compatible local model providers.

- Add custom/local provider prompts and atomic models registry updates.
- Preserve registry fields, validate endpoint configuration, and support Qwen thinking compatibility.
- Document setup and cover registry persistence with onboarding tests.

Files changed:
 .changeset/fn-8547-local-provider-onboarding.md    |   7 +
 docs/cli-reference.md                              |  13 ++
 docs/settings-reference.md                         |  35 ++++-
 packages/cli/src/commands/__tests__/onboard.test.ts |  55 ++++++-
 packages/cli/src/commands/onboard.ts               | 161 ++++++++++++++++++++-
 5 files changed, 261 insertions(+), 10 deletions(-)

Fusion-Task-Id: FN-8547

Fusion-Task-Lineage: 740221de-fce8-43ae-a095-13b8abf1904a

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-23 12:31:55 -07:00
gsxdsm
961edf2145 fix(dashboard): mount plugin routes without engine
Keep plugin-defined APIs reachable in UI-only dashboards by sourcing routes from the plugin loader when no engine runner exists.
2026-07-22 13:47:58 -07:00
gsxdsm
2f014f5c0c fix(cli): install agent-browser across Windows, Linux, and macOS (#2405)
## Summary

Fusion npm installs now expose a working `agent-browser` command on
Windows, Linux, and macOS. Fusion publishes a top-level shim backed by
the exact pinned native package, preserving the existing Browser
Verification probe contract on every supported desktop OS.

## Validation

- Added a packed consumer-install matrix for `ubuntu-latest`,
`macos-latest`, and `windows-latest`.
- Each platform executes npm's generated shim, verifies `agent-browser
0.26.0`, and asserts the matching native binary is installed.
- Confirmed the packed Fusion manifest, installed dependency, and
command output all retain the exact declared version pin.
- Passed 101 focused CLI workflow/package tests, full workspace lint and
typecheck, strict changeset validation, and a real macOS ARM64
packed-install smoke.

## Post-Deploy Monitoring & Validation

- Search task logs for `agent-browser not found on PATH`, `Missing
native executable`, and Browser Verification availability warnings.
- Healthy signal: npm installs on Windows, Linux, and macOS resolve
`agent-browser --version` without missing-shim or native-payload errors.
- Failure signal: command resolution errors, version-pin mismatches,
missing native payloads, or increased browser-verification fast-bails.
- Validation window: first release cycle after publish; owner: Fusion
maintainers.
- Mitigation: revert the dependency and top-level shim if install
compatibility regresses.

---

[![Compound
Engineering](https://img.shields.io/badge/Built_with-Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)


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

## Summary by CodeRabbit

* **New Features**
* Added cross-platform `agent-browser` installation through Fusion CLI.
  * Exposed an `agent-browser` command for Windows, macOS, and Linux.
* Pinned the included `agent-browser` version to ensure consistent
installations.

* **Bug Fixes**
* Improved reliability of platform-specific executable selection and
command setup across supported operating systems.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 13:37:44 -07:00
gsxdsm
241a5c94ea chore: bump @earendil-works/pi to 0.81.1 (#2399)
## Summary
- Bump `@earendil-works/pi-ai` and `@earendil-works/pi-coding-agent`
from **0.80.10 → 0.81.1** (exact matched pins).
- Update `pnpm-workspace.yaml` overrides so floating `*` consumers
(`droid-cli`, `pi-llama-cpp`, runtime plugins) stay on the same
ModelRuntime surface.
- Refresh pin-guard tests, package-config assertions, and FNXC notes for
the new pin.

## What's new in pi 0.81.x
- Qwen Token Plan providers
- Expanded usage accounting (tools/compaction/branch summaries)
- Resilient compaction retries + retry lifecycle events
- Full provider-extension registration API
- Built-in llama.cpp router management
- Provider/catalog fixes (Bedrock env credentials, OpenAI Responses
early-stream retry, Codex 272K defaults, extension stream-fallback
restore)

## Test plan
- [x] `scripts/check-pi-versions-pinned` (4/4)
- [x] Typecheck: core, engine, dashboard, cli, pi-claude-cli
- [x] `package-config.test.ts` (35)
- [x] `provider-registration.test.ts` (14)
- [x] `auth-storage-concurrency` + `model-registry-refresh` (15)
- [x] `register-model-routes-kimi-k3-supplemental` (1)
- [ ] CI gate green
- [ ] Spot-check Anthropic OAuth + API key session
- [ ] Spot-check openai-codex model picker / supplemental models

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

## Summary by CodeRabbit

* **Updates**
  * Updated the bundled Pi runtime to version 0.81.1.
* Added support for newer models and providers, including Qwen Token
Plan.
  * Improved usage accounting and session reliability.
* Strengthened compaction retry handling and provider catalog accuracy.
  * Added support for the expanded maximum thinking level.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-22 01:32:14 -07:00
gsxdsm
396090fc03 fix(startup): bound model registry refresh so dashboard cannot hang
Post-extension modelRegistry.refresh() had no timeout, so a hung remote
catalog fetch left the TUI on "Loading extensions…" forever. Use a shared
15s-bounded refresh across dashboard/serve/daemon and related registration paths.
2026-07-21 17:14:30 -07:00
gsxdsm
5f0502e166 FN-8452: reject invalid update flags and announce beta releases
Make update commands fail clearly on invalid arguments while helping stable users discover newer beta releases.

- Parse update and upgrade options strictly, rejecting unknown, duplicate, and malformed flags before running an update
- Show a live-registry beta availability notice for stable human-readable output without affecting JSON or cached results
- Add CLI coverage, beta bootstrap documentation, and a patch changeset

Files changed:
 .changeset/fn-8452-update-unknown-flags.md         |   7 +
 RELEASING.md                                       |   2 +-
 docs/cli-reference.md                              |   4 +
 docs/getting-started.md                            |   2 +
 packages/cli/src/__tests__/bin-update-args.test.ts |  75 ++++++++++
 packages/cli/src/bin.ts                            |  25 +---
 packages/cli/src/commands/__tests__/update.test.ts |  44 ++++++
 packages/cli/src/commands/update.ts                | 162 +++++++++++++++++++--
 8 files changed, 285 insertions(+), 36 deletions(-)

Fusion-Task-Id: FN-8452

Fusion-Task-Lineage: b29a1ce5-5a40-40ec-ac18-07107fa18344

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-21 10:30:19 -07:00
gsxdsm
3962222863 FN-8424: route CLI chat replies through inbox mail
Route agent replies to the correct CLI or dashboard mailbox with bounded polling deadlines.

- add reply-parent routing validation and CLI/dashboard inbox selection
- preserve named mailbox conversations while handling per-message reply deadlines
- document chat and inbox interfaces and cover deadline and routing regressions

Files changed:
 .changeset/fn-8424-cli-chat-reply-routing.md       |   7 +
 docs/agents.md                                     |  20 +-
 docs/cli-reference.md                              |  29 +-
 packages/cli/src/bin.ts                            |  15 +-
 packages/cli/src/commands/__tests__/chat.test.ts   | 262 +++++++++---------
 .../cli/src/commands/__tests__/message.test.ts     |  12 +
 packages/cli/src/commands/chat.ts                  | 293 ++++++++++++---------
 packages/cli/src/commands/message.ts               |  18 +-
 ...tools-send-message-recipient-validation.test.ts |  86 +++++-
 packages/engine/src/agent-heartbeat-prompts.ts     |   8 +-
 packages/engine/src/agent-tools.ts                 |  71 +++--
 11 files changed, 523 insertions(+), 298 deletions(-)

Fusion-Task-Id: FN-8424

Fusion-Task-Lineage: 28d0ef88-717e-4f39-8880-64d2fef94706

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-20 11:26:08 -07:00
gsxdsm
9db0ffc1f9 FN-8425: route CLI chat through agent inbox
Route CLI chat messages through durable, named agent mailbox conversations.

- Add conversation IDs and parsing for CLI chat sessions.
- Filter CLI chat history and replies by mailbox conversation identity.
- Surface conversation IDs to agents and document the inbox-based transport.

Files changed:
 .changeset/fn-8425-cli-chat-conversation.md        |   7 +
 docs/agents.md                                     |  11 +-
 docs/cli-reference.md                              |  15 +-
 packages/cli/src/__tests__/bin-chat-args.test.ts   |  34 +++++
 packages/cli/src/bin.ts                            |  45 ++----
 packages/cli/src/commands/__tests__/chat.test.ts   | 162 +++++++++++++++++++++
 packages/cli/src/commands/chat.ts                  | 132 +++++++++++++++--
 packages/core/src/types/messages.ts                |   6 +
 .../__tests__/agent-tools-read-messages.test.ts    |  48 ++++++
 packages/engine/src/agent-tools.ts                 |  10 +-
 10 files changed, 418 insertions(+), 52 deletions(-)

Fusion-Task-Id: FN-8425

Fusion-Task-Lineage: 5091f49f-1f12-4ff7-8d21-48f008cf984e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-20 10:20:41 -07:00
gsxdsm
81abe53957 FN-8430: simplify TUI log timestamps
Keep dashboard TUI log rows focused on useful message content.

- Show capture times as HH:MM:SS across list, expanded, and copied log output.
- Strip leading detailed upstream timestamps from list and clipboard text while preserving raw expanded messages.
- Add regression coverage and a patch changeset.

Files changed:
 .changeset/fn-8430-tui-log-timestamps.md           |  7 ++
 .../commands/dashboard-tui/__tests__/app.test.tsx  | 93 +++++++++++++++++++++-
 packages/cli/src/commands/dashboard-tui/app.tsx    | 33 ++++++--
 3 files changed, 125 insertions(+), 8 deletions(-)

Fusion-Task-Id: FN-8430

Fusion-Task-Lineage: b656dfa1-3c16-4a6f-8ba8-a936fdfdd42d

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-20 10:04:20 -07:00
gsxdsm
4f0d89e106 FN-8419: safeguard project partition reconciliation
Safely reconcile fallback and registered project partitions during dashboard startup.

- Merge duplicate partition rows with fallback data taking precedence.
- Validate unique indexes and foreign-key dependencies before rekeying.
- Bind safely after failed promotion and stop non-retryable dashboard failures.
- Add PostgreSQL reconciliation and supervisor coverage.

Files changed:
 .changeset/fn-8419-rekey-partition-merge.md        |   7 +
 packages/cli/src/bin.ts                            |   5 +-
 .../commands/__tests__/dashboard-supervise.test.ts |  16 +-
 packages/cli/src/commands/dashboard.ts             |  41 ++-
 .../src/__tests__/postgres/schema-applier.test.ts  | 178 +++++++++++-
 packages/core/src/async-secrets-store.ts           |   9 +-
 packages/core/src/index.ts                         |   8 +-
 packages/core/src/postgres-errors.ts               |   9 +
 packages/core/src/postgres/index.ts                |   5 +
 packages/core/src/postgres/migration-stamping.ts   | 318 ++++++++++++++++-----
 packages/core/src/postgres/startup-factory.ts      |  49 +++-
 packages/core/src/process-supervisor.ts            |   3 +
 packages/core/src/task-store/async-persistence.ts  |   7 +-
 13 files changed, 546 insertions(+), 109 deletions(-)

Fusion-Task-Id: FN-8419

Fusion-Task-Lineage: bfc54e40-a31e-4b61-b6ae-01eb147efde1

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-20 01:54:42 -07:00
gsxdsm
ddd1316644 FN-8413: add ACP SDK to published CLI dependencies
Ensure packed Fusion installs the ACP SDK required by the vendored Claude CLI extension.

- Declare the compatible ACP SDK version in the CLI runtime dependencies.
- Guard published-manifest dependency coverage and clean-install resolution with tests.
- Add a patch changeset for the published package fix.

Files changed:
 .changeset/fn-8413-pi-claude-cli-acp-sdk-dep.md    |  7 ++
 packages/cli/package.json                          |  1 +
 packages/cli/src/__tests__/package-config.test.ts  | 26 +++++++
 .../pi-claude-cli-publish-resolve.smoke.test.ts    | 81 ++++++++++++++++++++++
 pnpm-lock.yaml                                     |  3 +
 5 files changed, 118 insertions(+)

Fusion-Task-Id: FN-8413

Fusion-Task-Lineage: c3cb8cc1-0be8-4196-b43e-eacd5837105d

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-19 21:40:57 -07:00
gsxdsm
5c67b19cb2 FN-8394: rescue deterministic quarantined tests
Restore reliable test coverage and delete quarantined tests that could not be rescued.

- Replace process- and database-dependent tests with bounded dependency seams
- Restore stabilized CLI, dashboard, and plugin test coverage
- Remove unrescuable bundle and merge-worktree test suites and clear the quarantine ledger

Files changed:
 packages/cli/src/__tests__/bundle-output.test.ts   | 519 ------------
 .../src/commands/__tests__/task-lock-retry.test.ts |  10 +
 packages/cli/vitest.config.ts                      |   8 -
 .../TaskDetailModal.tab-persistence.test.tsx       |   2 +-
 .../__tests__/TaskDetailModal.test-helpers.ts      |   7 +
 .../src/__tests__/dev-server-process.test.ts       | 391 ++++-----
 packages/dashboard/src/dev-server-process.ts       |  22 +-
 packages/dashboard/vitest.config.ts                |  21 +-
 .../merge-reuse-task-worktree.slow.test.ts         | 876 ---------------------
 packages/engine/vitest.config.ts                   |   7 -
 .../src/__tests__/process-lifecycle.test.ts        |  21 +-
 .../fusion-plugin-grok-runtime/vitest.config.ts    |   2 -
 .../src/__tests__/async-quality-store.pg.test.ts   | 148 +++-
 plugins/fusion-plugin-quality/vitest.config.ts     |   3 +-
 scripts/lib/test-quarantine.json                   |  43 +-
 15 files changed, 323 insertions(+), 1757 deletions(-)

Fusion-Task-Id: FN-8394

Fusion-Task-Lineage: e949b33e-b8d5-4f73-a002-e550b97ee125

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-19 18:59:36 -07:00
gsxdsm
e4a032d9d9 FN-8399: expose incomplete migration status on dashboard
Expose durable SQLite-to-PostgreSQL migration state through dashboard health and banners.

- Read per-project running and failed migration markers from PostgreSQL
- Surface degraded migration state in health endpoints and dashboard banners
- Preserve migration context across CLI and runtime startup paths
- Document the recovery workflow and add a patch changeset

Files changed:
 .changeset/FN-8399-migration-status-dashboard.md   |  7 +++
 docs/storage.md                                    |  5 ++
 packages/cli/src/commands/daemon.ts                | 19 +++++++
 packages/cli/src/commands/desktop.ts               |  6 +++
 packages/cli/src/commands/serve.ts                 | 20 +++++++-
 packages/core/src/index.ts                         |  2 +
 packages/core/src/postgres/index.ts                |  2 +
 packages/core/src/postgres/sqlite-migrator.ts      | 43 ++++++++++++++++
 packages/dashboard/app/api/health.ts               |  7 ++-
 .../app/components/dashboard/DashboardBanners.tsx  | 18 ++++++-
 .../dashboard/__tests__/DashboardBanners.test.tsx  | 12 ++++-
 .../__tests__/dashboard-postgres-health.test.ts    | 45 +++++++++++++++++
 .../dashboard/src/dashboard-postgres-health.ts     | 58 ++++++++++++++++++++++
 packages/dashboard/src/server.ts                   | 27 ++++++++--
 packages/engine/src/project-engine-manager.ts      |  4 ++
 packages/engine/src/project-runtime.ts             |  9 +++-
 packages/engine/src/runtimes/in-process-runtime.ts |  1 +
 17 files changed, 275 insertions(+), 10 deletions(-)

Fusion-Task-Id: FN-8399

Fusion-Task-Lineage: e196d6c4-ea9a-48ba-bedc-9e6fa44c33d3

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-19 16:52:02 -07:00
gsxdsm
3e376b98ba FN-8369: centralize GitHub issue import deduplication
Use provenance-first deduplication consistently across dashboard, CLI, and extension GitHub imports.

- Prioritize persisted sourceIssue and legacy metadata over editable descriptions.
- Reuse the dashboard deduplication helper for CLI and extension imports.
- Prevent duplicate issue creation within a dashboard batch import.

Files changed:
 .changeset/fn-8369-github-import-dedup.md          |  7 ++++++
 packages/cli/src/__tests__/extension.test.ts       | 27 ++++++++++++++++++++++
 packages/cli/src/commands/__tests__/task.test.ts   | 20 ++++++++++------
 packages/cli/src/extension.ts                      | 25 +++++++++++---------
 packages/dashboard/src/__tests__/github.test.ts    | 26 ++++++++++++++++-----
 packages/dashboard/src/__tests__/routes-github.test.ts | 22 ++++++++++++++++++
 packages/dashboard/src/github.ts                   | 27 +++++++++++++---------
 7 files changed, 119 insertions(+), 35 deletions(-)

Fusion-Task-Id: FN-8369

Fusion-Task-Lineage: 8eb6d19d-bd0e-487a-9f11-8945d744b7df

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-19 16:07:34 -07:00
gsxdsm
b2c784b3f8 FN-8381: remove flaky dist-barrel test
Remove the repeatedly quarantined extension dist-barrel test while retaining source-level listing coverage.

- Delete the CPU-bound dist-barrel regression test after its fourth quarantine cycle.
- Remove its quarantine exclusion and ledger entry.
- Document retained source-level formatting and truncation coverage.

Files changed:
 .../src/__tests__/extension-dist-barrel.test.ts    | 204 ---------------------
 packages/cli/src/__tests__/extension.test.ts       |   4 +-
 packages/cli/vitest.config.ts                      |   6 +-
 scripts/lib/test-quarantine.json                   |   5 -
 4 files changed, 6 insertions(+), 213 deletions(-)

Fusion-Task-Id: FN-8381

Fusion-Task-Lineage: ba6e61e1-fba1-4308-9d9d-1d3f387aa5e9

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-19 14:14:12 -07:00
gsxdsm
954ebe816a FN-8362: isolate agent lifecycle pauses and backlog claims
Keep agent lifecycle transitions independent from task pause state and make automatic backlog pickup executor-first.

- Prevent agent stop, sleep, resume, and heartbeat recovery from changing assigned task pause state
- Limit engineer automatic backlog pickup to explicit opt-in while preserving explicit routing
- Document CLI and extension lifecycle behavior and cover the boundaries with regression tests

Files changed:
 .../cli/skill/fusion/references/extension-tools.md |  4 +--
 .../skill/fusion/references/fusion-capabilities.md |  4 +--
 packages/cli/src/__tests__/extension.test.ts       | 13 +++++++
 packages/cli/src/commands/__tests__/agent.test.ts  | 27 ++++++++++++--
 packages/cli/src/commands/agent.ts                 |  4 +++
 packages/cli/src/extension.ts                      |  8 +++--
 .../core/src/__tests__/agent-role-policy.test.ts   |  7 +++-
 packages/core/src/agent-role-policy.ts             |  7 ++++
 .../src/__tests__/heartbeat-executor.test.ts       | 35 +++++++++---------
 packages/engine/src/__tests__/self-healing.test.ts | 30 ++++++++++++++++
 packages/engine/src/agent-heartbeat.ts             | 42 ++++++++++++++++------
 packages/engine/src/self-healing.ts                | 12 +++++++
 12 files changed, 157 insertions(+), 36 deletions(-)

Fusion-Task-Id: FN-8362

Fusion-Task-Lineage: a1fb37f3-f95e-4eb7-9fbf-bdc0ab5d5f9e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-19 13:47:38 -07:00
gsxdsm
2302fb8a3d feat: add beta/stable release tracks with switchable update channel (#2345)
## Summary

Fusion can now ship on two release tracks. Betas are cut from `main` as
`vX.Y.Z-beta.N` (npm dist-tag `beta`, GitHub prerelease), stable
releases are promoted to a long-lived `release` branch and published to
`latest`, and users pick their track with the new `updateChannel` global
setting — via **Settings → General → Release channel** or `fn update
--channel <stable|beta>`. Previously everything was single-track: every
publish landed on `latest` and every update surface could only see it.

| | beta | stable |
|---|---|---|
| Cut from | `main` | `release` branch |
| Version | `X.Y.Z-beta.N` (changesets pre-mode) | `X.Y.Z` |
| npm dist-tag | `beta` | `latest` |
| GitHub Release | prerelease | latest |
| Homebrew tap / X draft | skipped | bumped / printed |

## How releasing works now

`pnpm release` prompts for the channel and **defaults to beta**, so
day-to-day releases are betas; stable is always an explicit choice.
Choosing stable from `main` triggers assisted promotion: the script
proposes the newest beta tag reachable from HEAD, verifies `release`
fast-forwards to it, then runs the whole stable release inside a
temporary git worktree on `release` — the primary checkout never leaves
`main`. Changesets pre-mode preserves changeset files across betas, so
the promoted stable release aggregates every changeset since the last
stable into one clean changelog entry.

## Design decisions

- **Every publish path names an explicit `--tag`.** A beta accidentally
landing on `latest` is the one unrecoverable failure of a dual-track
scheme, so nothing relies on npm's implicit default (`release.mjs`,
`version.yml`).
- **Beta channel resolves to semver-max of `latest` and `beta`**, so
beta users are offered each promoted stable once it overtakes their
prerelease. Switching beta → stable never downgrades; `fn update
--channel stable --force` is the explicit escape hatch.
- **One comparator instead of three.** CLI, dashboard, and desktop each
had their own `isRemoteNewer` that ignored prerelease identifiers —
`0.73.0-beta.2`, `-beta.3`, and `0.73.0` all compared equal, which
breaks the moment any beta exists. They now share full SemVer-precedence
helpers (`compareVersions`, `resolveUpdateTargetVersion`) from
`@fusion/core`.
- **Installs pin exact versions** (`@runfusion/fusion@0.73.0-beta.2`),
never a dist-tag, so an install can't silently land on the wrong track.
- **Desktop channels via electron-updater manifests.** Beta tags build
desktop artifacts with `publish.channel=beta` (emitting `beta*.yml`);
the app sets `channel`/`allowPrerelease` from the shared setting,
re-read on every manual check.
- **Update caches are channel-stamped** — a cache written for one
channel is never served to the other, so switching tracks takes effect
on the next check instead of after TTL.

## Test plan

- New unit coverage: SemVer precedence + channel resolution in
`@fusion/core` (30), channel behavior of the dashboard update check (28,
incl. 9 new) and `fn update` (16, incl. 8 new: persist `--channel`,
no-downgrade, `--force`, cache channel mismatch).
- `pnpm verify:fast` green (scoped typecheck, builds, CLI build, boot
smoke); desktop + settings-section suites green.
- `release.mjs` dry-run matrix exercised by hand: channel prompt
(default/override/invalid), branch preflights per channel,
assisted-promotion target selection, fast-forward guard against a
diverged `release` branch, and bootstrap when no `release` branch
exists.
- Not exercised live: an end-to-end publish (needs TTY authorization +
real npm publish). First real run is the first `pnpm release --channel
beta`.

---

[![Compound
Engineering](https://img.shields.io/badge/Built_with-Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
![Claude
Code](https://img.shields.io/badge/Fable_5-D97757?logo=claude&logoColor=white)


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

* **New Features**
* Added beta and stable release channels across CLI, dashboard, and
desktop updates.
* Users can select a channel via Settings or `fn update --channel
<stable|beta>` (stored as a global default).
* Desktop beta releases now generate beta update manifests and publish
as prereleases.
* **Documentation**
* Expanded release-track, settings, and CLI references to explain
channel semantics and workflows.
* **Bug Fixes**
* Updates now pin the resolved version per channel, improve version
comparison, and prevent unintended cross-channel downgrades unless
`--force` is used.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 13:34:25 -07:00
gsxdsm
aa401123ea fix: make Windows updates and CE personas reliable (#2340)
## Summary

Windows installs with slow native dependencies now get five minutes to
finish, and a real timeout is reported as an actionable terminal retry
instead of a wall of preceding npm deprecation warnings. Registry
`ETIMEDOUT` errors keep their network diagnosis, including after the
legacy-bin `--force` retry.

Compound Engineering personas are now included in the published CLI
bundle, with complete source-to-staged coverage for all persona
definitions and a clear startup error if the bundled assets are missing
or empty.

The PostgreSQL statement visible in the report was validated by the
existing real-Postgres schema reapply test. Its actual `caused by`
detail was truncated, so this PR deliberately makes no speculative
database change.

## Validation

- Dashboard updater tests: 22 passed
- CLI updater tests: 16 passed
- CE persona installer tests: 7 passed
- Published bundle persona assertion: passed against every source
persona
- CLI and CE plugin typechecks: passed
- Changed production/config lint and strict changeset validation: passed
- Real PostgreSQL schema reapply integration test: passed

---

[![Compound
Engineering](https://img.shields.io/badge/Built_with-Compound_Engineering-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)


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

* **Bug Fixes**
* Windows CLI and dashboard updates now allow up to five minutes for
installation and restore Compound Engineering agent personas during npm
installs.
* Update failures now surface clearer, terminal timeout guidance (while
preserving specific network connection diagnostics) and avoid misleading
“deprecated”/generic timeout text.
* Persona assets are reliably included in plugin builds and bunded
persona installation now errors clearly when definitions are missing or
empty.
* **Tests**
* Expanded update and bundling coverage for the new 5-minute timeout and
error-handling scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-19 12:16:12 -07:00
gsxdsm
4cc8002c97 FN-8341: rework planning interviews for reactive validation
Make planning interviews user-controlled and context-aware from validation through task creation.

- Replace fixed-depth and deepening checkpoints with reactive follow-up questions
- Add explicit planning validation and require it before creating or breaking down work
- Expose validation through the API, CLI, routes, types, documentation, and release notes
- Update localized copy and PlanningModeModal mocks and assertions for the revised workflow

Files changed:
 .changeset/fn-8341-deepening-checkpoint-removal.md |   7 +
 .changeset/fn-8341-planning-reactive-backend.md    |   7 +
 docs/dashboard-guide.md                            |   8 +-
 packages/cli/src/commands/task.ts                  |  12 +-
 packages/core/src/index.gate.ts                    |   2 +-
 packages/core/src/index.ts                         |   2 +-
 packages/core/src/types.ts                         |  38 +-
 packages/dashboard/app/api/legacy.ts               |  15 +-
 .../dashboard/app/components/PlanningModeModal.tsx | 192 +-----
 .../__tests__/PlanningModeModal.autosize.test.tsx  |   4 +
 .../__tests__/PlanningModeModal.initial.test.tsx   |  50 +-
 .../PlanningModeModal.planning-flow.test.tsx       | 442 +------------
 .../__tests__/PlanningModeModal.test-helpers.ts    |   1 +
 .../PlanningModeModal.ui-interactions.test.tsx     |   3 +
 .../__tests__/planning-infinite-interview.test.ts  | 221 +++++++
 packages/dashboard/src/planning.ts                 | 711 ++++++---------------
 .../src/routes/register-planning-subtask-routes.ts |  64 +-
 packages/i18n/locales/en/app.json                  |   1 -
 packages/i18n/locales/es/app.json                  |   1 -
 packages/i18n/locales/fr/app.json                  |   1 -
 packages/i18n/locales/ko/app.json                  |   1 -
 packages/i18n/locales/zh-CN/app.json               |   1 -
 packages/i18n/locales/zh-TW/app.json               |   1 -
 23 files changed, 536 insertions(+), 1249 deletions(-)

Fusion-Task-Id: FN-8341

Fusion-Task-Lineage: 102f88af-2675-43f5-8e08-5403a0e17da8

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-18 17:53:20 -07:00
gsxdsm
9bc0eb6943 fix(FN-8277): prevent replayed duplicate follow-up tasks (#2319)
## Summary

Repeated review and executor sessions could replay a follow-up creation
step and produce another live task whenever the wording changed. In the
incident behind this fix, 21 creation calls for three intended
follow-ups left 18 duplicate tasks.

Agent-created tasks now retain their parent and agent provenance across
step sessions, heartbeats, and the published CLI surface. Same-parent
paraphrases converge on the existing task through a serialized pre-check
and a database-backed intent claim, while distinct sibling actions
remain separate. Candidate lookup is parent-indexed, uniqueness failures
abort creation, and canonical reuse no longer emits misleading creation
audit events or workflow claims.

Related: FN-8277

## Validation

- Core duplicate guard and intake: 24 tests passed
- Engine task creation and heartbeat: 154 tests passed
- CLI extension: 66 tests passed, 95 skipped
- Core, engine, and CLI typechecks passed
- Core, engine, and CLI builds passed before rebase; the rebase was
conflict-free
- Scoped lint, strict changeset validation, and diff checks passed


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

* **Bug Fixes**
* Prevented retried agent steps from creating duplicate follow-up tasks.
* Improved parent-scoped deduplication for paraphrased follow-ups while
preserving distinct sibling actions.
  * Preserved parent-task and agent context for created follow-ups.
* Concurrent follow-up requests are now serialized/deduplicated so
duplicates link to the existing task instead of showing as newly
created.
* Updated agent follow-up/heartbeat activity so reused follow-ups no
longer appear in run results as fresh creations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-18 14:18:25 -07:00
gsxdsm
baa1baff9f FN-8283: add secret-scrubbed organization bundle CLI
Add portable, secret-scrubbed organization export and import workflows.

- Assemble agents, raw skills, routines, automations, and settings into versioned bundles.
- Add org-export and org-import CLI commands with dry-run and collision controls.
- Preserve existing skills by default or materialize suffixed destinations, with CLI coverage.

Files changed: .changeset/fn-8283-org-bundle.md                   |   7 +
 docs/cli-reference.md                              |   9 ++
 docs/secrets.md                                    |  10 ++
 packages/cli/src/__tests__/bin.test.ts             |  17 ++
 packages/cli/src/bin.ts                            |  26 ++-
 .../cli/src/commands/__tests__/org-export.test.ts  |  30 ++++
 .../cli/src/commands/__tests__/org-import.test.ts  |  31 ++++
 packages/cli/src/commands/org-export.ts            |  19 +++
 packages/cli/src/commands/org-import.ts            |  18 +++
 packages/core/src/__tests__/org-bundle.test.ts     |  69 ++++++++
 packages/core/src/index.ts                         |  17 ++
 packages/core/src/org-bundle.ts                    | 176 +++++++++++++++++++++
 12 files changed, 428 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-8283

Fusion-Task-Lineage: 93877746-8e57-4c79-bdbe-8e4ec7a2efe6

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-18 11:49:11 -07:00
gsxdsm
8d1620ea23 FN-8294: expose action-gated Mission hierarchy tools
Expose project-scoped Mission hierarchy operations to engine agents and eligible dashboard chat sessions.

- Add Mission, milestone, slice, and feature tool definitions backed by MissionStore
- Classify hierarchy mutations for action and permanent-agent approval gates
- Wire gated tool access through triage, executor, heartbeat, CLI, and chat lanes
- Cover tool availability, mutation gating, and chat integration with tests and documentation

Files changed:
 .changeset/fn-8294-mission-engine-tools.md         |   7 ++
 docs/missions.md                                   |   8 ++
 packages/cli/src/extension.ts                      |   2 +-
 .../dashboard/src/__tests__/chat-manager.test.ts   |  48 +++++++++
 packages/dashboard/src/__tests__/chat.test.ts      |   1 +
 packages/dashboard/src/chat.ts                     | 113 ++++++++++++++++++++-
 .../src/__tests__/agent-mission-tools.test.ts      |  43 ++++++++
 packages/engine/src/__tests__/triage.test.ts       |  34 ++++++-
 packages/engine/src/agent-heartbeat.ts             |   4 +-
 packages/engine/src/agent-tools.ts                 |  64 ++++++++++++
 packages/engine/src/executor.ts                    |   2 +
 packages/engine/src/gating-classifications.ts      |  11 ++
 packages/engine/src/index.ts                       |  17 ++++
 packages/engine/src/triage.ts                      | 111 ++++++++++++++++++++
 14 files changed, 457 insertions(+), 8 deletions(-)

Fusion-Task-Id: FN-8294

Fusion-Task-Lineage: ab0f248b-8a38-40e3-b297-79f9dbe18075

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-18 10:31:59 -07:00
gsxdsm
afb2ed0650 FN-8271: restore quarantined CLI tests under shard load
Restore affected CLI and engine tests by removing load-amplifying fixture work and synchronizing fake-timer recovery.

- Replace the dist-barrel PostgreSQL fixture with an injected in-memory task store.
- Move mission and goal tool coverage to the shared PostgreSQL harness and complete plugin-store mocks.
- Return rescued CLI and heartbeat tests to default lanes and clear their quarantine records.

Files changed:
 .../src/__tests__/extension-dist-barrel.test.ts    | 90 ++++++++--------------
 .../__tests__/extension-mission-goal-tools.test.ts | 27 ++++---
 packages/cli/src/commands/__tests__/plugin.test.ts | 11 +++
 packages/cli/vitest.config.ts                      | 21 +----
 .../src/__tests__/heartbeat-error-recovery.test.ts | 33 ++++----
 packages/engine/vitest.config.ts                   |  7 +-
 scripts/lib/test-quarantine.json                   | 78 +------------------
 7 files changed, 84 insertions(+), 183 deletions(-)

Fusion-Task-Id: FN-8271

Fusion-Task-Lineage: 212a3ec7-db6b-4e80-97c3-1c704822cf60

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-18 07:48:45 -07:00
gsxdsm
7c23771433 FN-8265: add task follow-up proposal creation
Enable configured ephemeral workers to propose and create follow-up tasks from mailbox messages.

- Add persisted task-proposal claim state, migrations, and async messaging APIs.
- Register task-proposal creation routes, SSE events, agent tool support, and CLI integration.
- Add mailbox creation controls, settings, documentation, localization, and regression coverage.

Files changed:
 .changeset/fn-8265-task-follow-up-policy.md        |   7 ++
 docs/dashboard-guide.md                            |   2 +-
 docs/settings-reference.md                         |  12 ++-
 packages/cli/src/extension.ts                      |  34 ++++---
 .../src/__tests__/postgres/sqlite-migrator.test.ts |  14 ++-
 .../postgres/task-proposal-claim.pg.test.ts        |  51 +++++++++++
 packages/core/src/async-message-store.ts           |  39 ++++++++
 packages/core/src/index.gate.ts                    |   4 +-
 packages/core/src/index.ts                         |   4 +-
 packages/core/src/message-store.ts                 |  46 ++++++++++
 .../core/src/postgres/migrations/0000_initial.sql  |   2 +
 .../migrations/0020_task_proposal_claim.sql        |   4 +
 packages/core/src/postgres/schema-applier.ts       |  13 ++-
 packages/core/src/postgres/schema/project.ts       |   3 +
 packages/core/src/settings-schema.ts               |  19 +++-
 packages/core/src/task-store/async-persistence.ts  |   2 +-
 packages/core/src/task-store/persistence.ts        |   4 +-
 packages/core/src/task-store/serialization.ts      |   1 +
 packages/core/src/task-store/task-creation.ts      |  51 +++++++++++
 packages/core/src/task-store/task-row-mappers.ts   |   2 +-
 packages/core/src/types.ts                         |  56 +++++++++++-
 packages/dashboard/app/api/legacy.ts               |   5 +
 packages/dashboard/app/components/MailboxModal.tsx |   4 +
 .../app/components/MailboxTaskProposal.css         |   3 +
 .../app/components/MailboxTaskProposal.tsx         |  33 +++++++
 packages/dashboard/app/components/MailboxView.tsx  |   4 +
 .../__tests__/MailboxTaskProposal.test.tsx         |  43 +++++++++
 .../app/components/settings/section-keys.ts        |   2 +-
 .../settings/sections/GeneralSection.search.ts     |  13 ++-
 .../settings/sections/GeneralSection.tsx           |  22 +++--
 .../settings-default-descriptions.test.tsx         |   4 +-
 .../routes/__tests__/task-proposal-routes.test.ts  |  99 ++++++++++++++++++++
 .../src/routes/register-messaging-scripts.ts       | 101 +++++++++++++++++++++
 packages/dashboard/src/sse.ts                      |   7 ++
 packages/engine/src/agent-tools.ts                 |  30 ++++--
 packages/engine/src/executor.ts                    |   9 +-
 packages/engine/src/step-session-executor.ts       |   8 +-
 packages/i18n/locales/en/app.json                  |   5 +
 38 files changed, 696 insertions(+), 66 deletions(-)

Fusion-Task-Id: FN-8265

Fusion-Task-Lineage: 4e864a2f-3485-4a54-8be7-1699b5479a94

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-18 03:48:38 -07:00
gsxdsm
0bf496ee7b FN-8268: resolve Claude runtime aliases in Vitest
Ensure CLI Vitest runs resolve plugin runtime sources without built distribution artifacts.

- Alias the Claude runtime source entry point for CLI tests.
- Preserve focused execution of explicitly requested quarantined tests.
- Extend workspace-resolution coverage for runtime provider aliases and absent dist outputs.

Files changed:
 packages/cli/src/__tests__/vitest-workspace-resolution.test.ts | 53 ++++++++++++++++------
 packages/cli/vitest.config.ts                                  | 23 +++++++++-
 2 files changed, 62 insertions(+), 14 deletions(-)

Fusion-Task-Id: FN-8268

Fusion-Task-Lineage: c6e27f58-0de6-49ac-aa90-05a9829c0ad6

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-18 02:15:09 -07:00
gsxdsm
0b6c4cd4ca feat(dashboard,desktop): show live database-migration progress during boot
The one-time SQLite→PostgreSQL migration runs inside createTaskStoreForBackend
before any HTTP server listens, so browsers saw "connection refused" and open
tabs failed silently for minutes. Now:

- CLI: a temporary holding server binds the dashboard port for the boot window,
  serving an auto-reloading "Database migration in progress" page and an
  /api/health payload with status "migrating" + structured progress; the port
  is handed off (awaited) to the real app.listen().
- Dashboard SPA: already-open tabs render the new MigrationInProgressBanner
  from the 15s health poll when status is "migrating".
- Desktop: LocalRuntimeManager publishes migration progress on
  DesktopRuntimeStatus via the new core onMigrationProgress option;
  DesktopLaunchGate shows the live label and extends its 30s startup timeout
  while progress advances (2min stall cap), in both boot and first-run flows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:09:56 -07:00
Victor Canô
9cafa045df fix(pr-merge): resolve repo from the project checkout, not process.cwd(), in PR-mode auto-merge (#2281)
## Summary

In a centrally-installed, multi-project Fusion server (one process
serving several repos, `process.cwd()` = the install dir, not any repo),
every task under `mergeStrategy: "pull-request"` fails at the auto-merge
stage with:

```
Could not determine repository. Specify owner/repo in params or run from a git repository with a GitHub remote.
```

PR creation from the dashboard and status polling work; only the
engine's automatic PR path fails. This is the **non-workspace sibling of
#1924** (FN-7610 routed workspace-mode tasks to direct merge but does
not cover regular multi-project tasks) and the completion of
#1797/FN-7133 (which fixed only the `getPrMergeStatus` arguments).

## Root cause

`GitHubClient.resolveRepo()` (`packages/dashboard/src/github.ts`) falls
back to a cwd-less `getCurrentRepo()` — i.e. `git remote get-url origin`
in `process.cwd()` — whenever a PR method is called without explicit
`owner`/`repo`. The engine merge path already resolves the correct repo
from the per-project cwd (`prRepo = getCurrentRepo(cwd)`, FN-7133) but
only threaded it into `getPrMergeStatus`. Every other GitHub call
omitted it:

- `processPullRequestMergeTask`: `findPrForBranch` / `createPr` /
`mergePr` on both the per-task and shared-branch-group paths
- `createGroupPrCallback` (group-PR promotion): `findPrForBranch` /
`createPr`
- `createPrNodeGithubOps` (`pr-create`/`pr-merge` workflow nodes):
cwd-less `getCurrentRepo()` persisted `entity.repo` as `""` (poisoning
the downstream `splitRepoSlug` consumers), and the git
push/`createPr`/`mergePr` ran against `process.cwd()`
- the engine's review-response run (`buildRespondCallback`):
`respondOps.getCwd` collapses to `process.cwd()` because no CLI
composition site wires `getTaskWorktree`, so its git ops and response
agent ran outside the project repo

In a central install the fallback throws; worse, if `process.cwd()`
happens to be inside some *other* git repo, it silently targets the
**wrong repository**.

## What changed

- `fix(pr-merge): thread repo identity into PR auto-merge GitHub calls`
— widens the CLI-local `GitHubOperations` interface (optional
`owner`/`repo`, already accepted by `GitHubClient`'s
`FindPrParams`/`CreatePrParams`/`MergePrParams`) and passes `prRepo` at
all six call sites in `processPullRequestMergeTask`.
- `fix(pr-merge): resolve group-PR repo from project cwd in
createGroupPrCallback` — resolves via `getCurrentRepo(cwd)` from the
callback input (same T4 pattern as `syncGroupPrCallback`) with a loud
failure instead of a silent wrong-repo fallback.
- `fix(pr-merge): resolve PR-node repo from task worktree instead of
process cwd` — `resolvePrSource` resolves from `task.worktree`, git ops
run in `getTaskWorktree(...) ?? task.worktree ?? process.cwd()`, and
`createPr`/`mergePr` pass `owner`/`repo` parsed from `entity.repo`.
- `fix(pr-merge): resolve review-response run cwd from the task
worktree` — the engine owns the store, so `buildRespondCallback` prefers
the task's recorded `worktree` for the response run's git ops + agent,
keeping `ops.getCwd` as the single-project fallback (defensive against
structural `PrNodeStore`s without `getTask`).
- Changeset (`@runfusion/fusion` patch, structured body) included.

Deliberately **not** done: a constructor-scoped default repo on
`GitHubClient` — one client instance is shared across all projects in a
central install (`serve.ts`/`daemon.ts`/`dashboard.ts`), so per-call
`owner`/`repo` is the only correct scope.

## Testing

- New regression tests simulate the central-install topology
(`getCurrentRepo` mocked as `(cwd?) => cwd ? repo : null`, exactly the
failing environment) and drive the merge flow end-to-end on the per-task
path, the shared-branch-group path, `createGroupPrCallback`, and all
three `createPrNodeGithubOps` ops, asserting every GitHub call carries
explicit `owner`/`repo` (45 tests in
`packages/cli/src/commands/__tests__/task-lifecycle.test.ts`, all
green).
- `packages/engine/src/__tests__/pr-respond-cwd-resolution.test.ts`
covers the respond-run cwd: worktree preferred, `ops.getCwd` fallback
when the task has no worktree, when the lookup fails, and when a
structural store has no `getTask`.
- Existing exact-argument assertions were extended to the new call
contract (no assertions weakened or removed).
- `pnpm lint`, `pnpm typecheck`, and `pnpm build` green locally; `pnpm
test:gate`'s engine-core suite green (294/294) — its PostgreSQL-backend
lane needs local PG credentials this environment lacks, so that lane
defers to CI. `pnpm verify:fast` (scoped typecheck/build + CLI build +
boot smoke) also passes.

## Repro

1. Install the CLI centrally; run the server from a dir that is not a
git repo, serving ≥1 project with a GitHub `origin` and `mergeStrategy:
"pull-request"`.
2. Run a task to completion and let it reach the merge stage.
3. Before this fix: the auto-merger throws `Could not determine
repository …` (tasks with a persisted PR poll fine but never merge).
Merging the same task from the Pull Requests tab succeeds, because the
dashboard route resolves the repo explicitly (`parseBadgeUrl(...) ??
getCurrentRepo(rootDir)`).

Full analysis: https://github.com/Tchori-Labs/Fusion/issues/4

---

Developed with Claude (co-authored on all commits).

https://claude.ai/code/session_01ChEa8SHFYNAzjCdFbwFMfh


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

- **Bug Fixes**
- Resolved pull request auto-merge failures in centrally installed,
multi-project deployments.
- Ensured explicit repository context (`owner/repo`) is used for pull
request lookup, creation, and merging throughout the merge workflow.
- Improved pull request response handling to prefer the task worktree
for working-directory resolution, with safe error behavior when task
details are unavailable.
- **Tests**
- Expanded coverage for multi-repository merge workflows and
worktree-based repository/cwd resolution in PR response handling.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-17 17:28:09 -07:00
gsxdsm
adb3eb3a37 FN-8224: add Claude ACP runtime support
Add a bundled Claude Code ACP runtime with model discovery and CLI distribution integration.

- Add the Claude ACP runtime plugin, bridge, tool forwarding, and tests.
- Register Claude model discovery and cached picker support in the dashboard.
- Stage the plugin in CLI and desktop packaging with its pinned dependency.
- Add workspace, lockfile, and release metadata.

Files changed:
 .changeset/fn-8224-claude-acp-runtime.md           |   7 +
 packages/cli/package.json                          |   1 +
 packages/cli/src/__tests__/bundle-output.test.ts   |  32 +-
 .../cli/src/plugins/staged-bundled-plugin-ids.ts   |   1 +
 packages/cli/tsup.config.ts                        |  40 +-
 .../core/src/plugins/bundled-plugin-install.ts     |   2 +
 packages/dashboard/package.json                    |   3 +-
 packages/dashboard/src/claude-model-cache.ts       | 185 +++++++
 packages/dashboard/src/routes.ts                   |   1 +
 .../dashboard/src/routes/register-model-routes.ts  |  11 +
 packages/dashboard/src/runtime-provider-probes.ts  |  15 +
 packages/dashboard/vitest.config.ts                |   4 +
 packages/desktop/scripts/workspace-tools.ts        |   1 +
 plugins/fusion-plugin-claude-runtime/README.md     |   7 +
 plugins/fusion-plugin-claude-runtime/manifest.json |   1 +
 plugins/fusion-plugin-claude-runtime/package.json  |   1 +
 .../src/__tests__/cli-spawn.test.ts                |   3 +
 .../src/__tests__/index.test.ts                    |   3 +
 .../src/__tests__/provider.test.ts                 |   8 +
 .../src/__tests__/runtime-adapter.test.ts          |   7 +
 .../src/__tests__/tool-bridge.test.ts              |  79 +++
 .../src/acp-settings.ts                            |  21 +
 .../src/acp/VENDORED.md                            |  30 ++
 .../src/acp/cli-spawn.ts                           | 188 +++++++
 .../src/acp/control-handler.ts                     | 302 ++++++++++++
 .../src/acp/event-bridge.ts                        | 308 ++++++++++++
 .../src/acp/fs-capabilities.ts                     | 263 ++++++++++
 .../fusion-plugin-claude-runtime/src/acp/index.ts  |  16 +
 .../src/acp/path-jail.ts                           | 229 +++++++++
 .../src/acp/process-manager.ts                     | 177 +++++++
 .../src/acp/prompt-builder.ts                      |  87 ++++
 .../src/acp/provider.ts                            | 542 +++++++++++++++++++++
 .../src/acp/runtime-adapter.ts                     | 190 ++++++++
 .../src/acp/sanitize.ts                            |  81 +++
 .../src/acp/tool-mapping.ts                        |  47 ++
 .../fusion-plugin-claude-runtime/src/acp/types.ts  | 189 +++++++
 .../fusion-plugin-claude-runtime/src/cli-spawn.ts  |  31 ++
 plugins/fusion-plugin-claude-runtime/src/index.ts  |  95 ++++
 .../src/mcp-forwarding.ts                          | 114 +++++
 .../src/mcp-schema-server.cjs                      | 155 ++++++
 plugins/fusion-plugin-claude-runtime/src/probe.ts  |   8 +
 .../fusion-plugin-claude-runtime/src/provider.ts   |   8 +
 .../src/runtime-adapter.ts                         | 424 ++++++++++++++++
 .../src/skill-loader.ts                            | 290 +++++++++++
 .../src/tool-bridge.ts                             | 264 ++++++++++
 plugins/fusion-plugin-claude-runtime/src/types.ts  | 142 ++++++
 plugins/fusion-plugin-claude-runtime/tsconfig.json |  10 +
 .../fusion-plugin-claude-runtime/vitest.config.ts  |  22 +
 pnpm-lock.yaml                                     | 131 ++++-
 pnpm-workspace.yaml                                |   1 +
 50 files changed, 4753 insertions(+), 24 deletions(-)

Fusion-Task-Id: FN-8224

Fusion-Task-Lineage: 4e626913-32bb-4baa-a142-816c7f4ee7c4

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-17 12:00:26 -07:00
gsxdsm
7f1b7e21c1 FN-8223: enforce full CLI quarantine ledger lockstep
Extend CLI quarantine validation across the entire ledger.

- Normalize package-relative Vitest exclusions to repository paths.
- Require each CLI configuration exclusion and ledger entry exactly once.
- Validate ISO 8601 quarantine timestamps.

Files changed:
 .../src/__tests__/lockstep-cli-quarantine.test.ts  | 52 +++++++++++-----------
 packages/cli/vitest.config.ts                      |  4 +-
 2 files changed, 27 insertions(+), 29 deletions(-)

Fusion-Task-Id: FN-8223

Fusion-Task-Lineage: 7abe719b-63bf-4dfa-8db6-e3dd7547c7fe

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-17 11:43:46 -07:00
gsxdsm
065890ca01 FN-8222: align CLI tests with current store APIs
Repair CLI test mocks and retry-reset expectations for the current store contracts.

- Add the backend store factory to the experiment-finalize mock.
- Provide global settings directory access in backup test stores.
- Assert all manual retry reset fields in task command tests.

Files changed:
 .../extension-experiment-finalize.test.ts          | 13 ++++++++++
 .../commands/__tests__/backup-lock-retry.test.ts   |  2 ++
 packages/cli/src/commands/__tests__/backup.test.ts | 17 ++++++++++++-
 packages/cli/src/commands/__tests__/task.test.ts   | 28 +++++++++++++++++-----
 4 files changed, 53 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-8222

Fusion-Task-Lineage: 033ce6a6-c699-409c-a59e-2d1f5e041cfc

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-17 11:37:32 -07:00
gsxdsm
b686fbd61d FN-8220: mock daemon model runtime setup
Align daemon startup mocks with the model runtime initialization path.

- Add the auth-storage setModelRuntime mock.
- Stub the Fusion model registry factory to use the shared test registry.

Files changed:
 packages/cli/src/commands/__tests__/daemon.test.ts | 9 +++++++++
 1 file changed, 9 insertions(+)

Fusion-Task-Id: FN-8220

Fusion-Task-Lineage: 6e00980f-2e24-4502-a4d8-e91849df33b9

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-17 11:24:02 -07:00