Android WebView returns 0 for env(safe-area-inset-top) by default, so the
CSS-only header inset had no effect on edge-to-edge devices (API 35+).
- Add @capacitor-community/safe-area@^7.0.0 (Capacitor 7 compatible); it
patches the webview so env(safe-area-inset-*) report real values. Enabled
natively (no JS init), so it works in remote/live mode too.
- capacitor.config.ts: declare SafeArea plugin (initialViewportFitCover).
- mobile-run-android.sh: idempotently enable EdgeToEdge in MainActivity after
cap add (android/ is generated/gitignored), required by the plugin.
Keep the Agents view focused when starting the selected agent from the dashboard TUI.
- Treat `s` as the selected-agent start command inside the Agents interactive view.
- Preserve `m` as the universal Main/status shortcut and keep the `s` alias outside Agents.
- Add regression coverage for Agents, non-Agents, status, and empty Agents shortcut behavior.
- Add a patch changeset for the published CLI package.
Files changed:
.changeset/fn-6491-tui-agents-start-key.md | 5 +
.../commands/dashboard-tui/__tests__/app.test.tsx | 102 ++++++++++++++++++++-
packages/cli/src/commands/dashboard-tui/app.tsx | 9 +-
3 files changed, 112 insertions(+), 4 deletions(-)
Fusion-Task-Id: FN-6491
Fusion-Task-Lineage: 82a9acab-6e4a-400c-80ee-54d051245b57
- Add scripts/mobile-run-android.sh + pnpm mobile:run:android (auto-detects
Android SDK + JDK 21, writes local.properties, reconnects network ADB
before deploy, supports remote backend via FUSION_SERVER_URL).
- Header.css: reserve env(safe-area-inset-top) so top app chrome no longer
draws under the OS status bar on edge-to-edge native shells (Capacitor
Android API 35+, iOS notch, PWA standalone). No-op on web/desktop.
- .gitignore: ignore generated packages/mobile/{ios,android} (stale paths
pointed at packages/dashboard/).
Replace raw dashboard RGB alpha colors with color-mix token expressions and guard the global CSS surface.
- Converted global theme shadow, state, mission, event, and theme-data alpha colors from raw rgba() to color-mix() expressions.
- Added CSS fixture loading for theme-data.css and a regression test banning raw rgb/rgba outside var() fallbacks across global app CSS.
- Documented the stricter dashboard styling rule for global and theme token CSS.
Files changed:
docs/dashboard-guide.md | 2 +-
.../__tests__/global-theme-css-no-raw-rgba.test.ts | 68 +++
packages/dashboard/app/public/theme-data.css | 573 +++++++++++----------
packages/dashboard/app/styles.css | 49 +-
packages/dashboard/app/test/cssFixture.ts | 7 +
5 files changed, 389 insertions(+), 310 deletions(-)
Fusion-Task-Id: FN-6489
Fusion-Task-Lineage: 7c466971-d15c-4382-a24b-1b32eea71974
Rescue the same-day core and CLI flaky quarantines by fixing their fixture seams instead of appeasing timeouts.
- Make the core concurrent-write lock helper release synchronously inside its child process so package load cannot delay the transient lock release.
- Close real CLI TaskStore fixtures before removing temp roots and switch mock cleanup to non-hoisted unmocking.
- Remove both test files from package quarantine excludes and clear the quarantine ledger while documenting the rescue pattern.
Files changed:
docs/testing.md | 2 ++
packages/cli/src/__tests__/extension-task-tools.test.ts | 17 +++++++++++++----
packages/cli/vitest.config.ts | 4 +++-
.../core/src/__tests__/store-concurrent-writes.test.ts | 8 +++++++-
packages/core/vitest.config.ts | 4 +++-
scripts/lib/test-quarantine.json | 13 +------------
6 files changed, 29 insertions(+), 19 deletions(-)
Fusion-Task-Id: FN-6486
Fusion-Task-Lineage: c90358c4-0549-4c68-8d17-2e2a336433b5
Quarantine the load-sensitive CLI extension task tools suite instead of widening Vitest timeouts.
- Add the extension task tools test file to the CLI Vitest quarantine exclude list.\n- Record the FN-6483 quarantine evidence in the deletion-ratchet ledger while preserving the existing core quarantine entry.\n\nFiles changed:\n packages/cli/vitest.config.ts | 5 +++++\n scripts/lib/test-quarantine.json | 5 +++++\n 2 files changed, 10 insertions(+)
Fusion-Task-Id: FN-6483
Fusion-Task-Lineage: 4a3be3b4-b2b4-4e05-afd0-564aa0d28c68
- P1 (Greptile): a tool-use break-early turn released the warm connection
(inUse=false) while conn.prompt() was still pending, letting the next turn
launch a concurrent prompt on the same ACP session (protocol corruption).
keepWarm now requires !sawToolCall, so a tool-use turn tears the connection
down like the non-reuse path; only a clean stop turn (prompt fully resolved
before finish) keeps it warm. + test.
- buildBridgeEnv: treat a whitespace-only auth var as absent (v.trim()), so a
blank higher-preference token can't shadow a real lower-preference one and we
never forward a useless blank token. + test.
- Auth-forwarding tests: clear ambient auth vars in beforeEach so a runner-env
token can't shadow the case under test (CodeRabbit).
- Doc: clarify the allow-list never carries API keys by default; the single
FUSION_CLAUDE_ACP_FORWARD_AUTH opt-in (default OFF) is the only exception.
348/348 pass, tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep a warm bridge connection + ACP session across turns of one
conversation (gated by FUSION_CLAUDE_ACP_REUSE=1, default OFF), so
multi-turn lanes skip the cold bridge/claude spawn and session/new
round-trip and send only the latest-turn delta (buildResumePrompt).
A stable router indirection serves each turn's handlers.
Addresses the adversarial review of the reuse path:
- P0: a warm-child death routes failure to the CURRENT owner turn via
router.fail, so a reuse turn fails fast instead of hanging until the
30-min inactivity timeout.
- P1: eviction is cache-identity-aware (evictCachedAcpConn only deletes
the map key when it still points at the entry), so a concurrent cold
turn / stale close handler / idle timer can't evict or kill a newer
live entry's child.
- P1: an empty resume delta cold-starts instead of issuing an empty
prompt that could hang.
- P2: a per-turn token drops cross-turn stray updates on the shared
warm connection.
- The idle reaper is unref'd so it never pins the process.
Default OFF → the cold path is functionally unchanged (reviewer-verified).
Adds multi-turn tests: reuse skips spawn+session/new, flag-off spawns
fresh, fail-fast on warm-child death, empty-resume cold fallback.
346/346 pass, tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- P2: event-bridge handleMessageDelta now consumes cache_read/cache_creation
tokens (parity with handleMessageStart) — the OQ3 usage path carried them but
they were silently dropped, understating cost for cached turns.
- P2: validate the untrusted bridge usage payload — coerce each field to a
finite, non-negative number before forwarding, so a malformed value
(string/NaN/negative) can't corrupt totalTokens/cost.
- Tests: usage now asserts cache tokens + totalTokens; new cases for malformed
usage, tool-use turns reporting zero usage, the ANTHROPIC_AUTH_TOKEN middle
precedence, and that the auth token is read from process.env (never a
caller-supplied value — no token substitution).
- Doc: state the auth-forwarding exposure trade-off in the code comment.
acp-driver 13/13; event-bridge tests green; typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Item 2 (OQ3): capture PromptResponse.usage from conn.prompt and feed it into
the bridge before finish(), so ACP-path turns report token usage/cost instead
of always zero. Zero-when-absent is safe; tool-use (break-early) turns
inherently report zero (the prompt result never resolves).
- Item 3 (R17): opt-in headless credential delivery. When
FUSION_CLAUDE_ACP_FORWARD_AUTH=1, buildBridgeEnv forwards a SINGLE Claude auth
token (CLAUDE_CODE_OAUTH_TOKEN > ANTHROPIC_AUTH_TOKEN > ANTHROPIC_API_KEY) from
the operator's launch env so a detached daemon (no login Keychain) can
authenticate. Default OFF — the secure no-secrets posture is unchanged.
acp-driver tests 9/9 (usage + the three auth-opt-in cases); typecheck clean.
Remaining: item 1 (connection reuse / resume latency).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /providers/claude-cli/status: asserts acp.{enabled,bridgeAvailable,active,
authFailed,authReason} reflect the FUSION_CLAUDE_ACP env + the bridge auth-failure
signal file, and that acp is inactive/clean when no bridge path is published.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CodeRabbit: spinner class `spin` -> `animate-spin` (matches the card's other
Loader2 usages).
- CodeRabbit (major): tighten auth-failure detection so it only fires when the
WHOLE turn is the short "Not logged in" message (<=80 chars), not when a long
legitimate answer merely mentions the phrase — avoids false positives.
- CodeRabbit (major): expand the auth-signal test to assert the full invariant —
set on a not-logged-in turn, clear (unlink) on a real response, and NOT flag a
long answer that mentions the phrase.
pi-claude-cli acp-driver 5/5; typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Greptile P2: `acp.active` now reflects the ACTUAL dispatch determinant
(FUSION_CLAUDE_ACP, which includes the operator force-override), not the
experimental flag alone — so the status isn't misleading when forced on/off.
- CodeRabbit/Greptile P2: add FNXC:ClaudeAcp comments to the new code blocks
per the AGENTS.md greppable-comment convention.
Already fixed in the prior commit (daa37d08c): the P1 "sticky env" / latch
(applyClaudeAcpEnable now recomputes each call + FUSION_CLAUDE_ACP_FORCE
override) and the enable->disable-on-same-env regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the bridged `claude` can't authenticate (detached daemon / no keychain),
the turn returns "Not logged in" instead of a real answer. Rather than silently
relay that, detect it and let the user choose.
- Driver: detect a "Not logged in"-only turn and write a cross-process signal
(fusion-acp-bridge-auth.json); a real response clears it (acp-driver test).
- Dashboard status: GET /providers/claude-cli/status reports
acp.authFailed + authReason from the signal.
- UI: the Claude CLI provider card shows an auth-failure banner with
"Use claude -p" (sets experimentalFeatures.claudeCliAcp=false) and
"I fixed auth — re-test", plus a fix hint (run `claude` to log in).
- Enable resolution now recomputes each call with an operator force-override
(FUSION_CLAUDE_ACP_FORCE), so the "Use -p" fallback takes effect on the next
turn — no restart. claude-acp-enable tests updated.
pi-claude-cli + engine tests green; dashboard typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GET /providers/claude-cli/status now returns an `acp` block:
{ enabled (experimental flag), bridgeAvailable (KTD10 published path), active
(enabled && acpEnabled && bridgeAvailable) } so operators can see whether Claude
CLI is routing through the ACP bridge vs `claude -p` — important for the
default-on rollout. Additive; typechecks clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the manual FUSION_CLAUDE_ACP env enable with an experimental feature
switch. `experimentalFeatures.claudeCliAcp` is ON by default (off only when
explicitly set false); the engine translates it into the FUSION_CLAUDE_ACP
dispatch the pi-claude-cli provider reads, at registerExtensionProviders time.
- Still fail-closed: with no bridge path published (acp-runtime plugin absent),
the provider falls back to `claude -p`.
- Explicit FUSION_CLAUDE_ACP env always wins (operator / test override).
- New testable helper claude-acp-enable.ts (6/6 tests); flag documented in the
core experimentalFeatures doc.
So with the acp-runtime plugin installed, Claude CLI now routes through the ACP
bridge by default; set experimentalFeatures.claudeCliAcp=false to force `-p`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
streamSimple routes to streamViaAcp ONLY when FUSION_CLAUDE_ACP=1 AND a bridge
path is provided; otherwise stays on the -p streamViaCli path. Covers the three
cases (off / flag-without-path / flag+path) and asserts the bridge path + env
are forwarded. 333/333 pi-claude-cli tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three-reviewer pass (correctness/security/reliability) on the highest-risk file.
P0:
- Break-early now gates on isPiKnownClaudeTool: Claude's internal ToolSearch
(used to load deferred MCP tools) no longer aborts the turn before the real
fn_* call. Surface+break works from both tool_call updates and
request_permission. New test replays the U9 [ToolSearch, fn_task_list] sequence.
- Downgrade a tool_use turn that surfaced zero pi tool calls -> stop (mirrors
provider.ts), so pi never dispatches non-existent tools.
- register the bridge child in the process registry (no orphan on teardown).
- inactivity timeout (30 min, re-armed per chunk) + per-RPC timeouts on
newSession (a hung bridge now ends the stream and dies).
P1:
- capture bridge stderr + child 'close' handler -> surface exit code/stderr
(no more silent, undebuggable failures).
- sanitize untrusted agent output: strip ANSI/control chars, per-chunk +
per-turn caps, bound tool ids/names (no terminal-escape injection / DoS).
- validate bridge path (absolute + exists) before spawn.
- preserve image content blocks in the prompt (flatten-to-text dropped vision).
P2:
- enforce the bridge env allow-list INSIDE the driver (HOME/PATH/terminal only),
not trusting the caller-supplied object.
Documented residual (kill-switch stays OFF until verified): the bridge's
tool-execution ordering and native-tool (Bash/Read/Write) execution-prevention
need a live behavioral test before any lane enables this path.
pi-claude-cli: 330/330 tests green; typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds streamViaAcp: a drop-in alternative to streamViaCli that drives Claude
through the claude-code-cli-acp bridge over ACP instead of `claude -p`. Returns
the same AssistantMessageEventStream, so streamSimple dispatches to either
transport behind a kill-switch (FUSION_CLAUDE_ACP=1 + an injected bridge path),
OFF by default — the live `-p` path is byte-for-byte untouched until soak.
- Full-history prompt every turn (buildPrompt) — the ACP path has no --resume (R13).
- Forwards schema-only MCP servers so Claude emits correct tool calls; breaks
early on the first tool_call (cancel turn, surface to pi) so the bridge never
executes Fusion's tools — mirrors the `-p` break-early pattern.
- Translation reuses the tested createEventBridge by synthesizing Claude stream
events from ACP session/updates, sharing pi sequencing + tool-name mapping.
- Bridge env forwards only HOME/PATH so `claude` authenticates from the login
session (R17); never inherited process.env or API keys.
Verified: 3/3 translation unit tests; real-bridge session/update shapes confirmed
(agent_message_chunk text + tool_call); 326/326 existing pi-claude-cli tests green;
typecheck clean.
Remaining for Route A: engine injection of the bridge path (KTD10), U12 picker/
auth/status, U13 workflow verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route A unblock + the first Route A code increment.
- U9 verdict recorded (plan OQ1 + docs/acp-contract.md): in an authenticated
interactive session the pinned claude-code-cli-acp 0.1.1 bridge forwards
session/new mcpServers to Claude, Claude invokes the forwarded Fusion tool,
and the call traverses the ACP permission gate (session/request_permission).
Both security-critical answers resolve positively — overturns the headless
NOT-GO chain (FN-6466/6467/6473/6476), whose only blocker was running
detached from the login keychain session.
- R17 (daemon auth) recorded and closed for the supported setup: creds are
macOS Keychain-only; the user's login-session fn daemon has keychain access
(the existing claude -p provider authenticates there), so the bridge does too.
- U10: thread an optional mcpServers list through the ACP runtime contract.
newAcpSession now forwards it (was hardcoded []); AgentRuntimeOptions (engine
+ plugin-local copy) gains the field; defaults to [] to preserve Route B's
read-only ask posture. Tool calls still route through the U5 permission floor.
Plugin typechecks clean; provider-session tests 12/12 (incl. 2 new forwarding
tests). U11-U13 (provider transport swap, picker/auth, workflow verify) remain.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Index the upstream ACP MCP forwarding sponsorship doc so the docs README and CLI index test keep it discoverable.
- Add the upstream ACP MCP passthrough and permission forwarding sponsorship doc link under Audit Reports.
- Add the upstream doc to the required docs README index coverage.
- Guard that CLI Printing Press docs remain indexed in Audit Reports only, not duplicated under Plugins.
Files changed:
docs/README.md | 5 +++++
.../cli/src/__tests__/docs-readme-index.test.ts | 21 +++++++++++++++++++++
2 files changed, 26 insertions(+)
Fusion-Task-Id: FN-6479
Fusion-Task-Lineage: f23afeba-a989-4552-8857-fe2984df6081
Ensure AI-merge clean-room directories are cleaned up even when setup fails before worktree registration.
- Move clean-room directory creation inside the cleanup guard so terminal setup failures can remove it.
- Skip cleanup only when no merge root was ever created, while still unregistering any registered paths.
- Add regression coverage for active-session registration failure after mkdtemp and before git worktree add.
Files changed:
.../ai-merge-worktree-cleanup.test.ts | 31 ++++++++++++++++++++++
packages/engine/src/merger-ai.ts | 21 ++++++++++-----
2 files changed, 45 insertions(+), 7 deletions(-)
Fusion-Task-Id: FN-6453
Fusion-Task-Lineage: 364b329b-c3e7-4902-8ca8-673360b0d9ac
Add a durable proof-point runbook for validating external plugins against released Fusion CLI builds.
- Document release selection, npm integrity capture, scaffold/build/test/install/enable validation, and pass/fail criteria for external plugin proof points.
- Link the runbook from the docs index so it remains discoverable with other plugin authoring docs.
- Extend the docs README index test to require the new proof-point runbook entry.
Files changed:
docs/README.md | 1 +
docs/plugins/external-proof-point-runbook.md | 211 +++++++++++++++++++++
.../cli/src/__tests__/docs-readme-index.test.ts | 1 +
3 files changed, 213 insertions(+)
Fusion-Task-Id: FN-6438
Fusion-Task-Lineage: 5c768d7e-4465-42ac-9535-f16dc42ab6d0
Enable mobile users to swipe across the agent detail tab strip.
- Restores horizontal touch panning on the agent detail tabs while preserving vertical pan behavior.
- Documents the mobile touch-action requirement next to the tab-strip CSS.
- Adds a mobile regression test covering pan-x touch-action and horizontal overflow.
Files changed:
packages/dashboard/app/components/AgentDetailView.css | 6 ++++++
.../__tests__/AgentDetailView.mobile-scroll.test.tsx | 15 +++++++++++++++
2 files changed, 21 insertions(+)
Fusion-Task-Id: FN-6450
Fusion-Task-Lineage: 7006af83-4490-4fe0-a357-7d19b7b522c0
Rescue the session reconnect SSE test by shutting down route-owned background work before temp cleanup.
- Isolate the focused API harness from TaskStore EventEmitter startup workers.
- Dispose API routes and scheduled AI session cleanup before deleting the test temp root.
- Remove session-reconnect from the dashboard quarantine ledger and vitest skip list.
Files changed:
.../src/__tests__/session-reconnect.test.ts | 22 ++++++++++++++++++----
packages/dashboard/vitest.config.ts | 6 ++++--
scripts/lib/test-quarantine.json | 5 -----
3 files changed, 22 insertions(+), 11 deletions(-)
Fusion-Task-Id: FN-6447
Fusion-Task-Lineage: a3fef28c-b92b-4f47-9181-a63bb31e4f48
Ensure the Secrets page root expands within the project content flex row on narrow screens.
- Let the Secrets view root flex to available width with a zero min-width.
- Preserve existing height and spacing behavior while avoiding intrinsic card-width collapse.
- Cover the mobile layout contract with a root-container CSS assertion.
Files changed:
packages/dashboard/app/components/SecretsView.css | 7 +++++++
.../dashboard/app/components/__tests__/SecretsView.mobile.test.tsx | 7 +++++++
2 files changed, 14 insertions(+)
Fusion-Task-Id: FN-6446
Fusion-Task-Lineage: 34f55d8e-ee57-432c-b1ce-165cc43333ee