Commit Graph

7875 Commits

Author SHA1 Message Date
gsxdsm
c21b907211 feat(FN-5633): reviewer also grades the commit message (retry-to-fix)
Add a 4th reviewer axis: the squash commit subject must concisely and
accurately summarize the actual changes. A poor/inaccurate message is an
ADVISORY-severity concern, so it drives a corrective re-merge (which rewrites
the message) up to maxReviewPasses, then lands — it never hard-blocks the merge
over a message nit. Structural correctness (task-id prefix + Fusion-Task-Id /
lineage trailers) remains deterministically guaranteed by
ensureCommitTaskMetadata regardless.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 20:21:06 -07:00
gsxdsm
7f536eb619 fix(FN-5633): guarantee the squash subject starts with the task id
ensureCommitTaskMetadata (was ensureTaskTrailersOnHead) now also prefixes the
commit subject with `<taskId>: ` when includeTaskIdInCommit is on, in addition
to ensuring the Fusion-Task-Id / lineage trailers — so the landed commit both
starts with the task id and carries the board-association trailers even if the
AI agent omits them. Single idempotent amend.

Test: asserts the landed subject matches /^FN-1: / when the agent committed
without a prefix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 20:18:41 -07:00
gsxdsm
c1d558c765 feat(FN-5633): AI merge commit summary + task trailers, verify-and-fix, editable prompt
- Commit message: the merge agent writes a concise change-summary subject (not
  just the task title), and every landed squash carries board-association
  trailers — `Fusion-Task-Id: <taskId>` plus the canonical lineage trailer when
  the task has a lineageId — guaranteed via an idempotent amend
  (ensureTaskTrailersOnHead) even if the agent omits them.
- Verify-and-fix: the merge prompt instructs the agent to run the project's
  tests / type-check / lint after resolving the merge and fix any NEW failure
  the merge introduced (not pre-existing breakage) before committing.
- Editable prompt: the AI merge agent's base persona is the editable "merger"
  role prompt (Settings -> Prompts); the clean-room / verification / trailer
  rules are always appended so a custom prompt can't drop them.
- Reviewer model: reviewer uses the project reviewer/validator model lane
  (resolveValidatorSettingsModel); the bespoke merger.reviewerModel setting is
  removed.

Changeset updated to cover all AI merger changes.

Tests: trailer present on landed commit (+ lineage when set), editable-prompt
incorporation, new-breakage verification wording.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 20:14:13 -07:00
gsxdsm
1153b0932e Gitignore .fusion and stray fusion.db files on fn init 2026-05-28 19:55:52 -07:00
gsxdsm
0e5ca34c61 refactor(FN-5633): AI merge reviewer uses the project reviewer/validator model
Drop the bespoke `merger.reviewerModel` setting. The AI merge reviewer now
resolves its model via `resolveValidatorSettingsModel` — the same
reviewer/validator lane used elsewhere (project validator → global validator →
project default) — falling back to the merger model only if that lane is empty.
Removes the setting from the type, schema default, and the Settings UI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 19:53:11 -07:00
gsxdsm
730cee8d61 Remove errant vertical gray line on regular chat input box 2026-05-28 19:52:16 -07:00
gsxdsm
fb27ead6b3 Recovery: re-land PR tab spacing tokens onto main 2026-05-28 19:50:53 -07:00
gsxdsm
b2dce7d852 Recovery: Re-land (Close source-imported GitHub issues on ta 2026-05-28 19:49:31 -07:00
gsxdsm
c722451a1c Recovery: re-land onto main 2026-05-28 19:48:41 -07:00
gsxdsm
2c4683a298 Recovery: re-land tablet TaskDetailModal width onto main 2026-05-28 19:45:27 -07:00
gsxdsm
009d569bdd Recovery: re-land fn goals CLI + pi tools onto main 2026-05-28 19:44:15 -07:00
gsxdsm
be0be507b3 fix(FN-5633): fail loudly when an executed, never-merged task has no branch
The AI merge path treated a missing task branch as a benign no-op. That's
correct when the task was never executed or already merged (branch cleaned up
on a re-process), but if the task WAS executed (a baseCommitSha was recorded)
and has no recorded merge, the branch should still exist — its work appears
lost. Now that case throws instead of silently marking the task done; the
benign cases still finalize as a no-op (reason "already-merged" / "no-branch").

Tests: executed+never-merged → throws; already-merged → no-op done;
never-executed → no-op done.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 19:28:57 -07:00
gsxdsm
29ac58f8d0 feat(FN-5633): standalone AI merge path (clean-room merge + AI reviewer)
New default merge path (merger.mode="ai"), self-contained in merger-ai.ts and
dispatched from ProjectEngine.onMerge instead of the legacy aiMergeTask pipeline
(kept for merger.mode="deterministic").

Flow: clean-room detached worktree at the target branch tip → AI agent merges
the task branch + squashes (resolving conflicts) → fresh read-only AI reviewer
audits with corrective retries (blocking vs advisory; advisory lands, unfixable
correctness hard-fails via AiMergeBlockedError; fail-safe verdict parsing) →
land via `git merge --ff-only` when the checkout is on the target (else
update-ref CAS) → sync the local checkout (stash → ff → restore; AI reconciles
a conflicting restore and keeps the original edits in a backup stash;
un-stashable dirt advances the ref + warns) → finalize (delete task branch —
never the integration branch — task→done, remove temp worktree).

- Per-task target branch honored (falls back to the default integration
  branch); local checkout synced only when on that target.
- Structurally immune to the dirty-clobber and stale-base/non-FF bug classes of
  the legacy path (clean room + FF-by-construction).
- Progress surfaced on the task status pill + task log stream.
- Clear error when the target branch has no local ref.

Settings: merger.mode / merger.reviewerModel / merger.maxReviewPasses, surfaced
in Settings → Merge; legacy merge-mechanics settings hidden when AI mode is on.

Tests: merger-ai.test.ts (verdict parser, clean merge, blocking hard-fail,
advisory land, empty no-op, target-branch isolation, missing-target error,
landSquash clean/other-branch/dirty-restore/AI-resolved). Legacy
merge-orchestration tests pinned to deterministic mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 18:50:23 -07:00
gsxdsm
e75c4dae28 fix(FN-5627): suppress ntfy notifications for transient merge failures the engine auto-recovers
Even with FN-5627's merger TOCTOU fix + transient-failure self-healing
sweep + safety-fallback auto-prerebase landed, the merger can still hit
transient failure classes (lease handoff races, brief same-SHA non-FF
advances) for tasks whose branches are particularly out-of-sync. The
self-healing sweep auto-recovers them within bounded budget \u2014 but each
individual failure cycle was firing a ntfy alarm before the recovery
cleared the failed state, producing user-facing alarm spam for tasks
that were never actually stuck.

Two layers of fix:

1. NotificationService.handleTaskUpdated now classifies task.error via
   the new shared classifyTransientMergeError helper before scheduling
   the deferred failure notification. Transient classes
   (lease-handoff-target-not-queued, spurious-concurrent-advance-same-sha)
   get logged as suppressed and never schedule a ntfy timer.

2. Defense-in-depth: fireDeferredFailureNotification re-classifies the
   error at dispatch time, so a failure scheduled before the suppression
   landed on a newer cycle still suppresses if the error matches a
   transient class.

The classifier itself moved from self-healing.ts to a new logger-free
transient-merge-error-classifier.ts module so consumers in
NotificationService don't pull createLogger through the import chain and
break test mocks of ../logger.js (per project-memory rule about new
modules using createLogger). self-healing.ts re-exports the symbol for
backward compatibility.

Log prefix for the recovery actions also changed from
'[FN-5627] Auto-recovering...' to 'Auto-recovered:' so that
NotificationService.maybeSuppressTransientFailedNotification's existing
/^Auto-recovered:/ log-prefix check cancels any already-scheduled failure
notification when the sweep runs mid-grace-window.

Tests (3 new):
- transient lease-handoff-target-not-queued failure NOT notified
- transient spurious-concurrent-advance-same-sha failure NOT notified
- genuine different-SHAs concurrent-advance still notifies (control)

Engine suite: 6166 tests pass.

Fusion-Task-Id: FN-5627
2026-05-28 15:39:23 -07:00
gsxdsm
694970b2f1 fix(FN-5627): always rebase behind branches before squash (safety fallback)
The FN-5627 default-threshold fix changed the prerebase threshold default
from 0 (never fire on commit-count) to 1 (fire on any divergence). But
that only affected projects WITHOUT an explicit threshold. Projects with
user-set values like 'prerebaseDivergenceThreshold: 50' continued to
skip prerebase for small divergences (e.g., 4 commits behind), so the
squash built against stale base and update-ref refused non-FF \u2014
producing the same-SHA spurious-concurrent-advance signature that
stranded FN-5626/FN-5628/FN-5633.

Root distinction missed earlier:
- prerebaseDivergenceThreshold is for USER-VISIBLE SEVERITY REPORTING
  (this branch is N commits behind, warn me).
- Engine correctness requires a SAFETY INVARIANT (any branch behind main
  MUST be rebased before squash or update-ref will fail).

These are independent concerns. The safety invariant must not be gated
on the user's threshold.

decideAutoPrerebase() now returns fire=true with reason
'safety-fallback-any-divergence' whenever commitsBehind > 0, after the
hot-file and threshold checks. The threshold path still wins the reason
label when its condition trips, so user-visible severity reporting is
unchanged for non-pathological cases.

Full opt-out remains prerebaseAutoEnabled=false (skips the safety
fallback; user accepts behind-branch merges will fail).
prerebaseDivergenceThreshold=0 is no longer a complete opt-out from the
commit-count gate \u2014 it only suppresses the threshold-based reason label.

Tests (4 updated/new):
- safety-fallback-any-divergence reason added to AutoPrerebaseDecision
- 4 commits behind with threshold=50 fires via safety fallback
- prerebaseAutoEnabled=false respects full opt-out
- threshold trip still wins reason label
- commitsBehind=0 returns no-divergence (unchanged)

Engine suite: 6163 tests pass.

In-flight: FN-5626, FN-5628, FN-5633 manually SQL-reset to mergeRetries=0,
status=null, error=null, transientRecoveryCount=0 so the next merger tick
(after engine restart picks up this code) auto-prerebases via safety
fallback and lands the work. Future occurrences self-heal automatically.

Fusion-Task-Id: FN-5627
2026-05-28 15:04:41 -07:00
gsxdsm
b96b0bc79a feat(FN-5632): add collision retry logic to fn update command
Adds collision retry logic to the `fn update` CLI command to handle binary path conflicts, including test coverage and a getting-started guide update.

Fusion-Task-Id: FN-5632

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5632
2026-05-28 14:50:15 -07:00
gsxdsm
6b27ab5aab fix(FN-5627): default auto-prerebase to fire when branch >=1 commit behind
decideAutoPrerebase() previously defaulted prerebaseDivergenceThreshold
to 0, which meant the threshold path NEVER fired unless the user
explicitly set a positive value. Only hot-file matches could trigger
prerebase.

The result: tasks whose branch was started against an older main tip
(because other tasks landed concurrently) skipped prerebase, built their
squash commit against the stale base, and then failed at git update-ref
because the squash didn't descend from current main. The merger correctly
detected this as non-fast-forward and threw
IntegrationBranchConcurrentAdvanceError, but with both 'expected' and
'observed' SHAs set to current main tip \u2014 because observedCurrentSha was
captured from the pre-update rev-parse, not post-failure. This produced
the misleading 'expected X, observed X' same-SHA error signature that
stranded FN-5632 stuck at mergeRetries=3 after the FN-5627 merger fix
and engine restart.

New default: prerebaseDivergenceThreshold = 1. Any branch behind by at
least 1 commit auto-rebases before squash. Users who want the legacy
never-fire behavior can explicitly set prerebaseDivergenceThreshold = 0.
Threshold comparison also changed from > to >= so an explicit threshold
of N rebases at N+ commits behind instead of N+1+.

The self-healing classifier comment for spurious-concurrent-advance-same-sha
is updated to note the signature can come from either pre-FN-5627
misclassification OR the legitimate post-FN-5627 non-FF path; the
auto-recovery sweep is unchanged because both cases self-heal cleanly
once prerebase fires on the retry.

Tests (3 new):
- Default threshold (undefined) fires at 1 commit behind
- Explicit threshold = 0 stays as opt-out (never fire on commit-count)
- Default threshold doesn't fire when branch is up-to-date

Engine suite: 6160 tests pass.

In-flight: FN-5632 manually SQL-reset to mergeRetries=0 / status=null
once more so the next merger tick (after engine restart picks up this
code) auto-prerebases and lands the work. Future occurrences self-recover.

Fusion-Task-Id: FN-5627
2026-05-28 14:21:38 -07:00
gsxdsm
5768d5ec45 feat(FN-5627): self-heal transient merge failures stuck at mergeRetries=3
After the FN-5627 merger fix (b2d547eae, 230f6f45b) landed, two in-review
tasks (FN-5628, FN-5632) remained stuck at mergeRetries=3 with
status=failed because the merger correctly identified transient failure
classes but had no auto-recovery path \u2014 the AUTO_MERGE_COOLDOWN_MS reset
takes hours and gives up too easily.

Failure classes covered:
- lease-handoff-failed: target-not-queued (FN-5353/FN-5363 race where the
  merge queue lease was cleared between enqueue and handoff acquisition).
- Legacy same-SHA spurious 'Integration branch X advanced concurrently
  (expected SHA, observed SHA)' errors from pre-FN-5627 code paths.

Implementation:
- New MergeDetails.transientRecoveryCount field tracks per-task recovery
  attempts, bounded by MAX_TRANSIENT_MERGE_RECOVERIES = 2.
- New classifyTransientMergeError() string matcher in self-healing.ts
  identifies recoverable classes by error pattern. Returns null for
  genuine merge failures (verification, conflicts, real concurrent
  advances with different SHAs).
- SelfHealingManager.recoverTransientMergeFailures() sweep finds
  matching in-review tasks, resets mergeRetries=0, clears status/error,
  increments recovery count, re-enqueues via requeueForAutoMerge.
- Wired into BOTH startup recovery and periodic Batch 2 maintenance loop.
- Emits merger:transient-failure-auto-recovered (recovered) and
  merger:transient-failure-budget-exhausted (terminal) audit events.

No-op when autoMerge=false, requeueForAutoMerge not wired, or pause
active. Repeat-suppression on budget-exhausted emit via error marker
[transient-recovery-budget-exhausted] to prevent log spam.

Tests (6 new):
- target-not-queued recovery path
- spurious-concurrent-advance-same-sha recovery path (legacy)
- genuine concurrent-advance (different SHAs) NOT recovered
- non-transient failures NOT recovered (verification, conflicts)
- budget exhaustion emits marker once, no further requeue
- autoMerge=false no-op

Engine suite: 6157 tests pass (6 new).

In-flight: FN-5628 and FN-5632 were manually reset via SQL so the
already-running engine (which has the FN-5627 merger fix) can re-attempt
their merges before this self-healing path lands and reloads. Future
occurrences self-recover.

Fusion-Task-Id: FN-5627
2026-05-28 13:53:15 -07:00
gsxdsm
5b5da2c24f feat(FN-5630): add bundled plugin installation support to CLI
Adds bundled plugin resolution for `@runfusion/fusion`, including a dist candidate check in `bundled-plugin-install.ts` and regression tests covering the install path, with a patch changeset prepared.

Fusion-Task-Id: FN-5630

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5630
2026-05-28 13:28:53 -07:00
gsxdsm
230f6f45b3 feat(FN-5627): auto-recover from fast-path foreign-commit refusal
Instead of immediately parking the task as failed when the auto-merge
fast-path detects a non-ancestor commitSha (the symptom of a pre-FN-5627
TOCTOU poisoning), clear the poisoned mergeDetails fields and re-enqueue
for a fresh aiMergeTask attempt. The branch typically still has the work
intact; the merger just needs to redo the squash + ref-advance with the
now-fixed flow.

Recovery semantics:
- mergeRetries < MAX_AUTO_MERGE_RETRIES (3): clear poisoned fields
  (commitSha, mergedAt, landedFiles, filesChanged, insertions, deletions,
  noOpVerifiedShortCircuit, landedFilesAttributionRestricted, mergeConfirmed),
  increment mergeRetries, clear status/error, re-enqueue via
  internalEnqueueMerge. Emit new merger:fast-path-auto-recovered audit event.
- mergeRetries >= MAX_AUTO_MERGE_RETRIES: terminal park as failed (existing
  behavior), with merger:fast-path-blocked-foreign-commit audit event
  carrying budgetExhausted=true.

The recoverable path keeps the task in in-review with status=null, so
downstream consumers (dashboard banner, ntfy notifications) don't surface
a transient failure for what should be a self-healing event. The terminal
path remains FN-4538/FN-5488 compatible: status=failed at retry ceiling
on in-review is recognized by clearStaleBlockedBy fast paths so
downstream todos don't deadlock.

Tests updated and added:
- FN-5627 auto-recover test: asserts mergeRetries=1, status=null, all
  poisoned fields cleared, no moveTask('done'), no task:merged emit.
- FN-5627 budget-exhausted test: asserts status=failed with descriptive
  error, mergeRetries=3 input, no moveTask('done').

Fusion-Task-Id: FN-5627
2026-05-28 13:19:26 -07:00
gsxdsm
b2d547eae5 fix(FN-5627): close merger TOCTOU + add fast-path reachability gate
The merger persisted `mergeConfirmed: true` + `commitSha` to the task row
as soon as the local squash commit was built, BEFORE running
`git update-ref refs/heads/<integration>` to actually advance the
integration branch. If the ref-advance then failed for any reason (lock
contention, hook rejection, packed-refs race, or a misclassified non-CAS
error via the merger-ref-update-advance.ts string heuristic), the task row
was poisoned: the auto-merge scheduler's mergeConfirmed fast-path would
silently promote the never-landed work to 'done' on the next tick,
including emitting task:merged and closing the linked GitHub tracking
issue. The 'expected SHA == observed SHA' log signature on FN-5625 was a
red herring — the ref-advance had failed for non-race reasons but the
string heuristic in merger-ref-update-advance.ts classified it as
'concurrent-advance', and the downstream IntegrationBranchConcurrentAdvanceError
routed through the unsafe 'merge already confirmed' recovery path.

This silently dropped real work on at least 9 tasks across 2026-05-27/28
(FN-5596, FN-5597, FN-5599, FN-5612, FN-5613, FN-5614, FN-5616, FN-5623,
FN-5625) and likely affected older now-archived tasks for which evidence
has been pruned.

Three-layer fix:

1. merger.ts (~9752): in reuseTaskWorktreeMerge mode, persist
   `mergeConfirmed: false` initially. After advanceIntegrationBranchRef
   returns advanced=true, do a follow-up updateTask to flip the flag.
   Other merge paths (legacy in-place, verified no-op fast-paths,
   owned-commit recovery) advance the ref BEFORE the mergeDetails write
   and remain unchanged.

2. project-engine.ts (~1378): defense-in-depth reachability gate on the
   auto-merge 'merge already confirmed' fast-path. Before moveTask to
   'done', verify `git merge-base --is-ancestor <commitSha>
   refs/heads/<integration>` succeeds. On failure, clear mergeConfirmed,
   set status='failed' with descriptive error, leave task in 'in-review',
   and emit `merger:fast-path-blocked-foreign-commit` run-audit event.
   Legitimate no-op merges (no commitSha) bypass the gate; ancient tasks
   missing mergeTargetBranch also bypass to avoid false-positive parks.

3. merger-ref-update-advance.ts (~189): replace fragile string heuristic
   ('is at' / 'expected' / 'cannot lock ref' in stderr) with structured
   detection. After update-ref fails, re-read the ref: if observed ==
   expected, classify as `ref-update-refused` (no race occurred); only
   classify as `concurrent-advance` when ref actually moved. Eliminates
   the misleading 'expected X observed X' same-SHA pair.

Tests: 3 new regression tests covering all three layers. Full engine
suite: 6150 tests pass.

Fixes:
- FN-5625 (autopilot validator trigger fix lost)
- FN-5623 (`fn goals` CLI lost)
- FN-5616 (source-issue close handlers lost)
- FN-5614 (`fn update` collision retry lost)
- FN-5613 (dashboard reload banner lost)
- FN-5612 (bundled-plugin-install lost)
- FN-5599 (tablet modal width lost)
- FN-5597 (ntfy notifier priority lost)
- FN-5596 (PR tab spacing test lost)

Fusion-Task-Id: FN-5627
2026-05-28 13:02:30 -07:00
gsxdsm
200dda95dc feat(FN-5624): suppress transient task.json ENOENT with guard, logging, and
Implements graceful suppression of transient `task.json` ENOENT errors in the executor, logging a suppression signal and surfacing a banner in the UI, with test coverage for both the executor behavior and notification service. Documentation in `docs/architecture.md` and a changeset for `@runfusion/f

Fusion-Task-Id: FN-5624

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5624
2026-05-28 12:10:03 -07:00
gsxdsm
2df891fd53 chore: changeset for release.yml auto-trigger on v* tags
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 10:48:35 -07:00
gsxdsm
2b66825fc1 test(core): force deterministic fs.watch failure with NUL-byte path
Prior attempt accepted either fs.watch failure path (sync throw vs async
error event) — but on Linux Node, fs.watch with `recursive: true` on a
missing directory silently succeeds (returns a no-op watcher, never
throws, never emits an error). Neither catch arm fires, so the warning
the test wants to assert never appears.

Switch to a NUL-byte-embedded path. Node validates the path argument up
front and throws ERR_INVALID_ARG_VALUE synchronously on every platform,
guaranteeing the `watch:fs-watch-setup` catch arm runs. Restore the
strict assertions on phase + message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 10:46:21 -07:00
gsxdsm
2a3535893b feat(FN-5622): add goals REST API with store accessor and route handlers
Introduces a Goals REST API (`GET/POST/PUT /api/goals` and `GET/PUT /api/goals/:id`) backed by a new `@fusion/core` goal store and typed goal types, including comprehensive route and store test coverage. Documentation on architecture and storage is updated to reflect the new domain, and a changeset

Fusion-Task-Id: FN-5622

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5622
2026-05-28 10:32:16 -07:00
gsxdsm
8c0a75f50e test(core): accept either fs.watch failure path on Linux and macOS
`store-activity.test.ts > "logs fs.watch setup failures and keeps polling
active"` flakes on Linux CI: it sets `tasksDir` to a non-existent path
and expects `fs.watch` to throw synchronously (routed to
`watch:fs-watch-setup`). macOS Node does throw, but Linux Node returns a
watcher that emits an async `error` event instead (routed to
`watch:fs-watch-error`). The contract this test guards is "log the
failure and keep polling alive" — not which catch arm handled it.

Match either warning message + phase, and use `vi.waitFor` so the async
Linux path isn't raced by the spy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 10:03:29 -07:00
gsxdsm
f13e197371 fix(dashboard): import JSX type from react for OAuthReloginBanner
React 19 removed the global JSX namespace; matches the pattern already used in agentHealth.tsx.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:14:33 -07:00
gsxdsm
e84673c632 feat(FN-5618): handle deleted source issues in TaskCard
Implements source issue delete handling in the task dashboard, including prompt-based user interaction for source-linked issues, new GitHub tracking state logic in `packages/dashboard/src/github-tracking-state.ts`, and corresponding test coverage, with documentation added to `docs/task-management.md

Fusion-Task-Id: FN-5618

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5618
2026-05-28 08:57:59 -07:00
gsxdsm
9969b0779b feat(FN-5619): expand PR notice block padding
Adds padding expansion to the PR notice block in PrPanel.css with corresponding test assertions to verify the token values, completing the FN-5619 styling pass.

Fusion-Task-Id: FN-5619

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5619
2026-05-28 08:45:09 -07:00
gsxdsm
0a04837e7a chore(release): v0.35.0
Version bump via changesets.
2026-05-28 08:20:50 -07:00
gsxdsm
e0a7dd7793 Merge pull request #1100 from plarson/feat/rtk-pi-bash-rewrite
feat(engine): add opt-in RTK bash rewriting
2026-05-28 08:13:54 -07:00
gsxdsm
d76b6f9503 feat(FN-5615): add dynamic system panel height to dashboard-tui
FN-5615 improves TUI token visibility by dynamically sizing the system panel height and clarifying the manual token copy hint, with regression tests added in the dashboard-tui app and a changeset for the patch release.

Fusion-Task-Id: FN-5615

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5615
2026-05-27 21:31:13 -07:00
gsxdsm
8a0fbf00b9 feat(FN-5611): strip react devtools from bun compile builds
Complete the standalone build pipeline fix by setting `DEV=false` during bun compile to eliminate React DevTools from production executables, with hardened regression tests and documentation.

Fusion-Task-Id: FN-5611

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5611
2026-05-27 21:31:13 -07:00
gsxdsm
bb19823ed4 feat(FN-5610): externalize cpu-features in Bun build
Externalizes the `cpu-features` dependency from the CLI build step to support Bun compilation compatibility, completing Step 2 of the migration.

Fusion-Task-Id: FN-5610

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5610
2026-05-27 21:31:12 -07:00
gsxdsm
7d2cf330db feat(FN-5609): add macOS desktop signing configuration and entitlements
Adds macOS code signing and notarization infrastructure (FN-5609), introducing hardened runtime entitlements, electron-builder config for signed builds, and signing-enabled release workflows, with tests validating the configuration and docs covering the signing flow.

Fusion-Task-Id: FN-5609

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5609
2026-05-27 21:31:12 -07:00
gsxdsm
925a466043 feat(FN-5608): add linux arm64 dual-arch support to desktop release workflo
Adds Linux ARM64 as a target architecture to the desktop release pipeline, including dual-arch release and test-release workflows, electron-builder configuration updates, and corresponding tests and documentation.

Fusion-Task-Id: FN-5608

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5608
2026-05-27 21:31:12 -07:00
gsxdsm
9504c31a85 feat(FN-5607): add cross-platform updater feeds for desktop releases
Add multi-platform updater feed configuration for the desktop app, including Windows, Mac, and Linux feed files with a collector, wired into the release and test-release workflows, plus corresponding tests and documentation.

Fusion-Task-Id: FN-5607

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5607
2026-05-27 21:31:12 -07:00
gsxdsm
a0d30f18f8 feat(FN-5606): add ARM64 to release and test-release workflow matrices
Adds ARM64 (Apple Silicon) as a target platform in both the release and test-release CI matrices, with corresponding test assertions covering the updated workflow configurations.

Fusion-Task-Id: FN-5606

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5606
2026-05-27 21:31:12 -07:00
gsxdsm
294efb02fd feat(FN-5605): add linux gpg signing for release artifacts
Implements Linux GPG signing for the release pipeline by adding a `sign-linux.sh` helper, wiring it into the release and test-release workflows, including `.asc` signature files in release collectors, and documenting the full signing workflow in CODE_SIGNING.md with a note in the desktop README.

Fusion-Task-Id: FN-5605

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5605
2026-05-27 21:31:12 -07:00
gsxdsm
d767e2ecbd feat(FN-5601): add OpenAI Responses API type support to custom providers
Added OpenAI Responses API as a new custom provider type, wiring `apiType: "responses"` through the core registry, engine routes, and dashboard UI with a dropdown selector; includes test coverage across the registry, routes, and component layers.

Fusion-Task-Id: FN-5601

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5601
2026-05-27 21:31:12 -07:00
gsxdsm
da34bd06e3 feat(FN-5595): add oauth relogin banner with validity logger
This merge implements an OAuth relogin banner feature (FN-5595) that displays in the dashboard when OAuth tokens expire. The feature includes a new `OAuthReloginBanner` component with styling and tests, an OAuth validity logger in the engine for tracking token state, and corresponding API route inte

Fusion-Task-Id: FN-5595

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5595
2026-05-27 21:31:12 -07:00
gsxdsm
0d0d07ded4 feat(FN-5603): add desktop release jobs for macOS and Linux with artifact a
Added GitHub Actions workflows for macOS and Linux desktop release artifacts (`release.yml` and `test-release.yml`), wired in new `dist` scripts across platforms, and extended test coverage for electron-builder config and release workflow logic, with updated documentation for the new artifacts.

Fusion-Task-Id: FN-5603

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5603
2026-05-27 21:31:12 -07:00
gsxdsm
699556d5a0 Merge pull request #1106 from titosemi/fix/inbox-user-alias-normalization
fix(core): normalize bare "user" id to dashboard alias in inbox routing
2026-05-27 16:30:04 -07:00
gsxdsm
b69c968ddc Merge pull request #1099 from titosemi/fix/chat-load-latest-messages
fix(dashboard): load latest messages first; fix scroll-to-bottom and pagination
2026-05-27 14:35:59 -07:00
Phil Larson
199204989e feat(engine): add opt-in RTK bash rewriting 2026-05-27 11:51:05 -07:00
Josemi Liebana
dd6c361083 fix(core): normalize bare "user" id to dashboard alias in inbox routing
Messages sent with to_id="user" (the natural alias agents use) were
silently dropped from getInbox() due to two gaps:

WRITE PATH (types.ts): normalizeMessageParticipant did not include "user"
in DASHBOARD_USER_ALIASES, so messages were stored with toId="user"
instead of toId="dashboard".

READ PATH (message-store.ts): getParticipantIdsForLookup did not include
"user" in the IN-clause for the dashboard mailbox lookup, so messages
already stored with toId="user" (from old sessions) were never returned.

Both paths now include "user" as a canonical alias for DASHBOARD_USER_ID.

Tests:
- normalizeMessageParticipant("user","user") -> {id:"dashboard",type:"user"}
- getInbox("dashboard","user") returns messages stored with toId="user"
  (legacy case: inserted directly into DB without normalization)
2026-05-27 18:47:52 +02:00
Josemi Liebana
6ad18663b8 fix(dashboard): address review comments on PR #1099
- fix(perf): use messagesRef.current in loadMoreMessages to avoid
  recreating callback on every streamed token; messagesRef is already
  kept in sync on every render so no useEffect needed — removes
  `messages` from useCallback deps
- fix(api): reject invalid order query param with 400 instead of
  silently ignoring; valid values are 'asc' and 'desc'

Tests:
- useChat: loadMoreMessages identity is stable when messages array changes
- chat-routes: GET /messages?order=invalid returns 400
2026-05-27 17:38:04 +02:00
Josemi Liebana
d7e4cf7f94 fix(test): update useChat tests for order=desc and before-cursor pagination 2026-05-27 15:24:31 +02:00
Josemi Liebana
599b1ec337 fix(dashboard): scroll-to-bottom button now overrides pending scroll restoration
When the user clicks 'Latest' while loadMoreMessages was in-flight,
the scroll restoration (which maintains position when older messages
are prepended) was undoing the explicit jump-to-bottom.

Fix: clear scrollRestoreSnapshotRef and set isUserScrollingRef=false
before anchorToBottom so the layout effect's restoration is skipped.
2026-05-27 14:43:51 +02:00
Josemi Liebana
6e9ab0e989 feat(dashboard): load latest messages first with order=desc pagination
- Add order?: 'asc' | 'desc' to ChatMessagesFilter (core)
- Route handler passes order param from query string
- Initial load requests order=desc and reverses for display
- loadMoreMessages uses before cursor instead of offset
- hasMoreMessages starts false (prevents IntersectionObserver race)
- ChatView IntersectionObserver guards against messagesLoading
2026-05-27 11:57:21 +02:00