Fork conversations across agent harnesses - #9025
austinywang wants to merge 188 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds cross-harness conversation forking with transcript export, compaction, retention-aware loading, target-harness selection, workspace routing, finite-choice command-palette input, transcript popover support, localization, and Xcode package/source integration. ChangesCross-harness conversation forking
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested reviewers: Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (4 errors, 1 warning)
✅ Passed checks (20 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Sources/AgentConversationForkRequest.swift`:
- Around line 30-32: Replace the rawValue comparison in usesNativeFork(for:)
with a typed conversion from RestorableAgentKind to TargetHarness. Add an
initializer or equivalent explicit switch over RestorableAgentKind that returns
the matching TargetHarness or nil, and use that result alongside the existing
.current check so future enum cases require an explicit mapping decision.
In `@Sources/AgentConversationSource.swift`:
- Around line 72-75: Update AgentConversationSourceAdapter.read and every
adapter implementation to use `@concurrent` nonisolated async, ensuring delegated
SessionTranscriptLoader.load and SessionIndexStore.resolveConversationEntry work
executes concurrently off the caller’s executor. Preserve the existing return
and throwing behavior; alternatively encapsulate this work in an owned
background actor/service.
In `@Sources/ContentView.swift`:
- Around line 9086-9099: Update commandPaletteArgumentInstruction to use
String.localizedStringWithFormat instead of String(format:) when applying the
localized instruction format, preserving the existing format string and
arguments so step numbers receive locale-aware formatting.
- Around line 7742-7760: The fork conversation command and its dialog argument
strings lack translations across all supported locales. Update the localization
entries for command.forkAgentConversation.title,
commandPalette.argument.instruction, the forkConversation argument labels, and
all forkConversation harness options in Resources/Localizable.xcstrings so each
of the 21 supported locales has a translation, preserving the existing English
and Japanese values.
In `@Sources/GhosttyNSView`+ForkConversationContextMenu.swift:
- Around line 59-90: Extract the duplicated destination-item construction into a
helper such as makeDestinationSubmenu(representedObjectFor:) that creates each
destination item, assigns forkCurrentAgentConversation(_:), self, and the
supplied represented object. Replace both the current-harness submenu
construction and the per-harness loop in the menu-building method with this
helper, preserving the current-harness default-destination state configuration
and each cross-harness AgentConversationForkRequest.
- Around line 60-63: Update the Localizable.xcstrings catalog entry for
terminalContextMenu.forkConversationWith to include translations or appropriate
localization states for all 20 supported locales, matching the existing catalog
conventions; alternatively remove unsupported unused locale entries so the
catalog accurately reflects supported localization coverage.
In `@Sources/Workspace`+AgentConversationForkRequest.swift:
- Around line 16-38: The fork failure paths in forkAgentConversation, including
the export catch and missing startup input validation, currently return false
without user-facing feedback. Add an appropriate user-visible failure
notification for these paths and ensure the context-menu invocation also
surfaces failures, while preserving the existing false return behavior and
success flow.
- Around line 22-24: Update the error logging in the conversation export failure
path to avoid exposing error.localizedDescription with public privacy. Keep the
snapshot kind logging unchanged, but mark the dynamic error description as
private or otherwise redact it according to the project’s logging conventions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 0f6ed85a-0745-4943-8fae-363f7bd899db
⛔ Files ignored due to path filters (1)
cmux.xcworkspace/contents.xcworkspacedatais excluded by!**/*.xcworkspace/contents.xcworkspacedata
📒 Files selected for processing (46)
Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Vault/Providers/HermesAgent/HermesAgentIndex.swiftPackages/macOS/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/Vault/Providers/HermesAgent/HermesAgentIndexTests.swiftPackages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Handling/CommandPaletteCommandContribution.swiftPackages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Handling/CommandPaletteHandlerRegistry.swiftPackages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteArgumentCollection.swiftPackages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteChoiceArgument.swiftPackages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteCommand.swiftPackages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteMode.swiftPackages/macOS/CmuxCommandPalette/Tests/CmuxCommandPaletteTests/CommandPaletteArgumentCollectionTests.swiftPackages/macOS/CmuxConversationTransfer/Package.swiftPackages/macOS/CmuxConversationTransfer/README.mdPackages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/ConversationCompaction.swiftPackages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/ConversationFormatting.swiftPackages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/ConversationRole.swiftPackages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/ConversationTransferPolicy.swiftPackages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/ConversationTransferService.swiftPackages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/ConversationTurn.swiftPackages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/RoleLabeledConversationFormatter.swiftPackages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/TailPreservingConversationCompactor.swiftPackages/macOS/CmuxConversationTransfer/Tests/CmuxConversationTransferTests/ConversationTransferServiceTests.swiftResources/Localizable.xcstringsSources/AgentConversationExportService.swiftSources/AgentConversationForkRequest.swiftSources/AgentConversationSource.swiftSources/CommandPalette/CommandPaletteArgumentChoiceView.swiftSources/CommandPalette/CommandPaletteDebugFormatting.swiftSources/CommandPalette/CommandPaletteOverlayContainerView.swiftSources/CommandPalette/WeakWindowReference.swiftSources/ContentView+ForkAgentConversation.swiftSources/ContentView.swiftSources/GhosttyNSView+ForkConversationContextMenu.swiftSources/RestorableAgentSession.swiftSources/SelectedWorkspaceDirectoryObserver.swiftSources/SessionIndexModels.swiftSources/SessionIndexStore+ConversationResolution.swiftSources/SessionIndexStore.swiftSources/SessionIndexView.swiftSources/SessionRestorableAgentSnapshot+Commands.swiftSources/SessionTranscriptPopoverHost.swiftSources/SessionTranscriptRetention.swiftSources/Workspace+AgentConversationForkRequest.swiftSources/Workspace+ForkConversationContextMenu.swiftSources/Workspace.swiftcmux.xcodeproj/project.pbxprojcmuxTests/AgentConversationCrossHarnessForkTests.swiftcmuxTests/CommandPaletteSearchEngineTests.swift
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
Packages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteChoiceArgument.swift (1)
18-21: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winEnforce non-empty, unique choice values.
An empty
choicesarray enters argument-selection mode with no selectable row, so the command cannot complete. Duplicate values are also unsafe becauseCommandPaletteArgumentChoiceViewuseschoice.valueas the row ID while selection matches by that same value. Reject invalid declarations at construction/registration and cover both cases with tests.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Packages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteChoiceArgument.swift` around lines 18 - 21, The CommandPaletteChoiceArgument initializer currently accepts empty or duplicate choices, causing invalid selection state and non-unique row IDs. Update CommandPaletteChoiceArgument initialization or registration to reject empty choices and duplicate choice.value entries, then add tests covering both invalid declarations while preserving valid choice handling.Packages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteArgumentCollection.swift (1)
37-47: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winReject duplicate argument names before using them as dictionary keys.
valuesis keyed byargument.name, so duplicate names are ambiguous. Selecting the first declaration can make a later declaration appear collected and return.completedwithout presenting it. Validate uniqueness during collection construction (or make construction fail) and add a regression test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Packages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteArgumentCollection.swift` around lines 37 - 47, Validate that argument names are unique when constructing the argument collection, before collection logic uses them as dictionary keys; reject or fail construction for duplicates so later declarations cannot be treated as already collected. Update the relevant initializer or factory near the validation of arguments and add a regression test covering duplicate names.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Sources/CommandPalette/CommandPaletteDebugFormatting.swift`:
- Around line 26-31: Update commandPaletteDiagnosticKeyEventSummary and
commandPaletteDiagnosticTextPreview to avoid including user-entered character
content in diagnostics. Replace character previews with redacted metadata only,
such as whether each value is present and its byte count, while preserving the
existing event type, key code, and modifier metadata.
---
Outside diff comments:
In
`@Packages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteArgumentCollection.swift`:
- Around line 37-47: Validate that argument names are unique when constructing
the argument collection, before collection logic uses them as dictionary keys;
reject or fail construction for duplicates so later declarations cannot be
treated as already collected. Update the relevant initializer or factory near
the validation of arguments and add a regression test covering duplicate names.
In
`@Packages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteChoiceArgument.swift`:
- Around line 18-21: The CommandPaletteChoiceArgument initializer currently
accepts empty or duplicate choices, causing invalid selection state and
non-unique row IDs. Update CommandPaletteChoiceArgument initialization or
registration to reject empty choices and duplicate choice.value entries, then
add tests covering both invalid declarations while preserving valid choice
handling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 6b12fef6-1b3f-4418-89b5-a9dd72fd1ba5
📒 Files selected for processing (23)
Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Vault/Providers/HermesAgent/HermesAgentIndex.swiftPackages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteArgumentCollection.swiftPackages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteArgumentSelectionResult.swiftPackages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteChoice.swiftPackages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Values/CommandPaletteChoiceArgument.swiftPackages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/ConversationCompacting.swiftPackages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/ConversationCompaction.swiftPackages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/ConversationTransferError.swiftPackages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/ConversationTransferService.swiftSources/AgentConversationForkRequest.swiftSources/AgentConversationForkTargetHarness.swiftSources/CommandPalette/CommandPaletteDebugFormatting.swiftSources/ContentView.swiftSources/PopoverAnchorView.swiftSources/SelectedWorkspaceDirectoryObserver.swiftSources/SelectedWorkspaceDirectorySnapshot.swiftSources/SessionIndexView.swiftSources/SessionTranscriptLatestCollector.swiftSources/SessionTranscriptPopoverCoordinator.swiftSources/SessionTranscriptPopoverHost.swiftSources/SessionTranscriptRetention.swiftcmux.xcodeproj/project.pbxprojcmuxTests/AgentConversationCrossHarnessForkTests.swift
💤 Files with no reviewable changes (3)
- Packages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/ConversationCompaction.swift
- Sources/SessionTranscriptRetention.swift
- Packages/macOS/CmuxConversationTransfer/Sources/CmuxConversationTransfer/ConversationTransferService.swift
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Sources/SessionIndexView.swift (1)
1272-1369: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy liftUnbounded full-file scans introduced by the new "opening + latest" retention mode. The cross-harness-fork retention feature (
retention.keepsLatestTurns) reads potentially the entire transcript file for the generic JSONL agents and adds a second unbounded scan for antigravity's opening-turn lookup, instead of reusing the bounded tail-paging already implemented elsewhere in this same PR.
Sources/SessionIndexView.swift#L1272-L1369:finishLine()/the streaming loop never setsdidHitTurnLimitwhenretention.keepsLatestTurns, so Claude/Codex/Grok/registered transcripts are always read forward to EOF to find the "latest N" turns; consider a tail-based read strategy analogous tofromTailPagesinstead of a full forward scan.Sources/SessionIndexView.swift#L1409-L1419: the additionalSessionIndexJSONLReader().fromStart(url:)call to find the opening user turn has no byte bound; consider using the existingmaxBytesoverload with a generous cap and treating a miss as best-effort, rather than scanning the whole history file.Sources/SessionIndexJSONLReader.swift#L11-L17: the newfromStart(url:body:)overload itself is the unbounded primitive enabling the above; if callers need a true full scan it's fine as a utility, but its cross-harness-fork usage should be bounded.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Sources/SessionIndexView.swift` around lines 1272 - 1369, Bound the cross-harness-fork transcript reads instead of scanning full files: in Sources/SessionIndexView.swift:1272-1369, replace the keepsLatestTurns forward streaming path with the existing bounded tail-page strategy analogous to fromTailPages, preserving latest-turn ordering and retention behavior; in Sources/SessionIndexView.swift:1409-1419, call the bounded maxBytes overload for the opening user-turn lookup with a generous cap and treat misses as best-effort; in Sources/SessionIndexJSONLReader.swift:11-17, retain fromStart(url:body:) only for intentional full scans and ensure this feature uses the bounded overload.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Sources/SessionIndexView.swift`:
- Around line 1409-1419: Bound the opening-turn lookup in the
retention.keepsLatestTurns branch by using the existing maxBytes-capable
SessionIndexJSONLReader.fromStart overload instead of the unbounded overload.
Reuse the appropriate existing byte limit, while preserving the session ID and
agent matching logic and best-effort assignment to openingUser.
---
Outside diff comments:
In `@Sources/SessionIndexView.swift`:
- Around line 1272-1369: Bound the cross-harness-fork transcript reads instead
of scanning full files: in Sources/SessionIndexView.swift:1272-1369, replace the
keepsLatestTurns forward streaming path with the existing bounded tail-page
strategy analogous to fromTailPages, preserving latest-turn ordering and
retention behavior; in Sources/SessionIndexView.swift:1409-1419, call the
bounded maxBytes overload for the opening user-turn lookup with a generous cap
and treat misses as best-effort; in Sources/SessionIndexJSONLReader.swift:11-17,
retain fromStart(url:body:) only for intentional full scans and ensure this
feature uses the bounded overload.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 89ed76a0-2342-41ed-bc38-cfec5a684af3
📒 Files selected for processing (11)
Resources/Localizable.xcstringsSources/AgentConversationForkTargetHarness.swiftSources/AgentConversationSource.swiftSources/CommandPalette/CommandPaletteDebugFormatting.swiftSources/ContentView.swiftSources/GhosttyNSView+ForkConversationContextMenu.swiftSources/RovoDevTranscriptPreview.swiftSources/SessionIndexJSONLReader.swiftSources/SessionIndexView.swiftSources/Workspace+AgentConversationForkRequest.swiftcmuxTests/AgentConversationCrossHarnessForkTests.swift
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Sources/AgentConversationForkTargetHarness.swift (1)
16-21: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep harness brand names out of localization.
Claude Code,Codex, andOpenCodeare product names; render them as literals rather than translatable catalog values.Proposed fix
case .claude: - String(localized: "forkConversation.harness.claude", defaultValue: "Claude Code") + "Claude Code" case .codex: - String(localized: "forkConversation.harness.codex", defaultValue: "Codex") + "Codex" case .opencode: - String(localized: "forkConversation.harness.opencode", defaultValue: "OpenCode") + "OpenCode"Based on learnings: brand/product name literals should not be wrapped in
String(localized:).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Sources/AgentConversationForkTargetHarness.swift` around lines 16 - 21, Update the harness-name mapping in the relevant switch so the .claude, .codex, and .opencode cases return their product names as plain string literals, removing the String(localized:) wrappers while preserving the displayed names.Source: Learnings
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@Sources/AgentConversationForkTargetHarness.swift`:
- Around line 16-21: Update the harness-name mapping in the relevant switch so
the .claude, .codex, and .opencode cases return their product names as plain
string literals, removing the String(localized:) wrappers while preserving the
displayed names.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f1e8b3ca-619f-432b-b357-4ccc81dcd0f1
📒 Files selected for processing (6)
Packages/macOS/CmuxCommandPalette/Sources/CmuxCommandPalette/Context/CommandPaletteContextKeys.swiftSources/AgentConversationForkTargetHarness.swiftSources/ContentView.swiftSources/GhosttyNSView+ForkConversationContextMenu.swiftSources/Workspace+AgentConversationForkRequest.swiftcmuxTests/AgentConversationCrossHarnessForkTests.swift
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
…ess-fork # Conflicts: # cmux.xcodeproj/project.pbxproj
…ess-fork # Conflicts: # Sources/ContentView+ForkAgentConversation.swift # Sources/Workspace+ForkConversationContextMenu.swift
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
Summary
Installed harnesses
Discovery covers Claude Code, Codex, Grok, OpenCode, OMP, Campfire, Pi, Amp, Cursor, Gemini, Kiro, Antigravity, Rovo Dev, Hermes Agent, Copilot, CodeBuddy, Factory, Qoder, and Kimi Code. Only executables found in configured or standard search paths appear. Ollama is excluded because deterministic launch requires a model argument.
This machine discovers Claude Code, Codex, Grok, OpenCode, OMP, Campfire, Pi, Amp, Cursor, Gemini, Hermes Agent, and Kimi Code.
Design
AgentConversationForkRequestowns harness plus destination for every UI entrypoint.CmuxConversationTransferowns role labeling, opening-plus-latest compaction, and a 24,000-byte UTF-8 bound.Floating command palette
#8351 by @lawrencecchen remains open. This PR does not stack on it because a merge-tree trial produced broad conflicts from unrelated stale branch state. The declarative command-argument model is the compatibility seam, so the same harness and destination steps can render in the current sheet or that floating window after it lands.
#8653 by @lawrencecchen supplied prior art for transcript adapters and fork routing. This branch reimplements the flow on current main with deterministic source binding and the shared transfer package.
Verification
fksamesucceeded atac00f543e1git diff --check, policy checks, and all 10 review threads are clean/resolvedThe speculative merge gate ran at https://github.com/manaflow-ai/cmux/actions/runs/31127039990. Workflow guard, remote daemon, web typecheck, React, web database migrations, tests-build-and-lag, and the feature-specific conversation-transfer stage passed. The aggregate app-host check failed across broad current-main tests. A clean
origin/mainrun reproduced the same three Kimi failures at the same assertions, confirming those failures are inherited.Closes #9016