Add iOS agent event feed - #10064
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:
📝 WalkthroughWalkthroughThe PR adds an interactive, paginated Agent Feed across macOS and iOS. It adds structured question handling, Workstream history APIs, RPC authorization, offline caching, multi-Mac aggregation, deep linking, localized SwiftUI presentation, and automated validation. ChangesAgent Feed and Workstream pipeline
Estimated code review effort: 5 (Critical) | ~120 minutes Mergeability Score: 🟠 High · up to The PR adds cross-device feed, approval, and structured-reply flows, but the current head still has a possible build failure plus concrete risks of exposing secrets, mis-handling approvals or form values, crashing on valid payloads, and blocking or starving feed/history work. The PR is not merge-ready without fixes or explicit owner acceptance of these high-impact issues. Possibly related issues
Possibly related PRs
Suggested reviewers: Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (9 errors, 1 warning)
✅ Passed checks (15 passed)
✨ Finishing Touches 💡 1📝 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 |
|
Caution CodeRabbit couldn't update its existing comment. The review summary may be out of date. Error details |
There was a problem hiding this comment.
Actionable comments posted: 34
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Sources/Feed/FeedCoordinator.swift (1)
2196-2222: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winParse
toolInputJSONonce per item instead of seven times.For each permission item,
itemDictparses the sametoolInputJSONstring repeatedly: once incodexCapabilityToolInputJSON, once insafeToolInputSummary, and once inside each of the fiveFeedPermissionActionPolicy.supports*calls.itemDictruns for every item of a history page, so a full page multiplies these parses.Compute the capability set once and reuse it for
supported_modes.Also applies to: 2341-2348
🤖 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/Feed/FeedCoordinator.swift` around lines 2196 - 2222, Update the item-dictionary construction around codexCapabilityToolInputJSON and supported_modes to parse toolInputJSON once per permission item, compute the shared capability set once, and reuse it for safeToolInputSummary and all FeedPermissionActionPolicy.supports* checks. Apply the same reuse pattern to the corresponding logic near the additional reported location.
🤖 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 `@CLI/FeedEventClassifier.swift`:
- Around line 147-148: Update the call to wireMapping in feedEventSemantic to
pass the normalized sourceKey instead of the raw source, ensuring
isSideEffectingTool receives canonical values such as "kiro" and preserves the
registered tool aliases for approval-card events.
- Around line 178-201: The question-event spelling table is duplicated and
divergent, causing inconsistent classification. In CLI/FeedEventClassifier.swift
lines 178-201, replace isQuestionEventName with the shared spelling-table call;
in
Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamStore.swift
lines 616-640, replace isQuestionEvent with that same shared call, preserving
the existing normalization behavior and using one canonical list.
In `@cmuxTests/MobileHostAuthorizationTests.swift`:
- Around line 771-800: Add a positive parameterized test alongside
testScopedAttachTicketRejectsFeedMutationOutsidePinnedRoute, covering both
workstream.feed.action and workstream.feed.reply with a scoped ticket whose
workspace_id and surface_id match the ticket’s pinned workspace and terminal;
assert ticketAuthorizationError returns nil.
In `@cmuxTests/MobileHostWorkspaceTicketAuthorizationTests.swift`:
- Around line 363-365: Update the authorization assertions for the
MobileHostRPCRequest entries in the scoped-ticket test: treat
workstream.feed.action and workstream.feed.reply as forbidden when params are
empty, while keeping agent-feed-list behavior unchanged. Alternatively, include
the required workspace_id in those requests to exercise the authorized
pinned-workspace route.
In `@ios/cmuxUITests/AgentFeedUITests.swift`:
- Around line 123-128: The correctness tests use runtime latency thresholds that
can fail under CI load. In ios/cmuxUITests/AgentFeedUITests.swift lines 123-128,
remove the frameP95, visibilityP95, frameStalls, and visibilityStalls assertions
while retaining the frames and visibility invariants and attaching latency
fields for inspection; in
Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileAgentFeedTests.swift
lines 303-332, remove the doubledTwice versus baseline comparison while
retaining the output-count assertion and printed benchmark line. Move threshold
evaluation to the dedicated performance job.
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift`:
- Line 10799: Update the foreground agent feed refresh flow around
scheduleForegroundAgentFeedRefresh(client:) to also run when
runtime?.supportsServerPushEvents is false. Trigger an initial refresh and
continue scheduling periodic refreshes for non-push runtimes, while preserving
the existing push-event listener behavior and avoiding duplicate refresh
scheduling.
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+AgentFeed.swift:
- Around line 140-142: Update the catch blocks in sendAgentFeedAction and
sendAgentFeedReply to map errors to the existing curated failure reasons (host
unreachable, timed out, rejected, or unsupported) before assigning
agentFeedMutationStates[item.id] = .failed(message:). Remove the raw
String(describing: error) value and reuse the repository’s established
workspace-action failure mapping convention.
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+SecondaryPromotion.swift:
- Around line 625-637: Update the warm-demotion flow around
resetForegroundAgentFeedIfInstanceChanged to remove the old bare Agent Feed
snapshot keyed by previousForegroundID before or alongside the tagged-feed
refresh. Preserve the existing foreground notification and agent-feed reset
behavior, and add regression coverage confirming the stale bare snapshot is
removed when a different Mac is promoted.
In
`@Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellAgentFeedPagingTests.swift`:
- Around line 63-66: Bound the paging loop in MobileShellAgentFeedPagingTests
around refreshAgentFeed and loadOlderAgentFeed with an explicit maximum
iteration count, increment the counter on each load, and assert the bound is not
exceeded while preserving the agentFeedCanLoadOlder termination condition.
In
`@Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileAgentFeedAggregation.swift`:
- Around line 7-15: Update items(from:) so merging retains only the best
Self.maxItemCount candidates instead of sorting all unique items before
truncation. Use Self.precedes to determine which candidates remain, then sort
the bounded set with the same comparator before returning it; preserve
newest-per-ID selection and existing tie-break behavior.
In
`@Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkstreamFeedListItem.swift`:
- Around line 197-212: Extend MobileWorkstreamDecision and decodeDecision to
support kind "boolean" by decoding its Boolean value into .boolean(value:), then
update the resolved-row rendering path to display that preserved value instead
of treating it as unknown.
In
`@Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkstreamFeedPayload.swift`:
- Around line 189-198: Update decodeScalarString to use non-throwing scalar
decoding attempts so numeric default_value inputs reach the numeric branches
instead of throwing. Check that integral Double values are within Int bounds
before conversion, preserving decimal formatting and avoiding traps for
out-of-range values. Add fixtures covering numeric and out-of-range
default_value cases.
In `@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentFeedRow.swift`:
- Around line 1025-1034: Update displayAnswer to use the shared
normalizedInputType helper instead of comparing the raw input type, ensuring
every value normalized to secret—including password—is redacted. Extract or
reuse the helper across AgentFeedRow and AgentFeedRowCopy so both mappings
remain consistent.
- Around line 212-215: Update the switch in AgentFeedRow to use
MobileAgentFeedItem’s isTurnCompletion model property instead of duplicating the
lifecycle "sessionEnd" check. Preserve the existing requiresResponse gating for
stop completions, and ensure every completion recognized by the model renders
replyComposer when the upstream filter marks it replyable.
- Around line 465-469: Update the date_time validation branch in
fieldValueIsValid to reuse a single cached ISO8601DateFormatter instance instead
of constructing one for each validation call. Hoist the formatter to appropriate
shared or instance scope while preserving the existing date parsing and
string-length checks.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentFeedView.swift`:
- Line 32: Document the intentional optional collection on
AgentFeedView.renderedItems: nil means no snapshot has been taken and should
fall back to source.visibleItems, while [] represents an empty snapshot. Add a
short explanatory comment or a targeted SwiftLint disable-next directive without
changing the optional behavior.
In
`@Packages/iOS/CmuxMobileSupport/Tests/CmuxMobileSupportTests/UITestConfigTests.swift`:
- Around line 438-449: Update agentFeedPreviewAcceptsEnvironmentOrLaunchArgument
to wrap the two enabled expectations in a `#if` DEBUG branch, while retaining
assertions that the flag is disabled in the non-DEBUG branch, matching
agentChatPreviewFlagIsDebugOnly and the build-configuration contract.
In
`@Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamPayload.swift`:
- Around line 8-10: Update every permission-mode allow-set that maps agent
decisions, including emitKiroDecisionIfHandled and any Swift or JavaScript
consumers using WorkstreamPermissionMode, to include persistent alongside once,
always, all, and bypass. Preserve explicit allow-set matching and fail closed
for unrecognized modes, while leaving deny behavior unchanged.
In
`@Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamQuestionPrompt`+Parsing.swift:
- Around line 156-199: Map boolean default values in the defaultValue resolution
near rawDefaultValue and resolvedOptions to the generated option ids: true to
"yes" and false to "no", while preserving existing choice normalization and
other input types. Update
WorkstreamQuestionPromptParsingTests.parsesBooleanConfirmation to expect the
corresponding generated id instead of "1".
- Around line 225-230: Update scalarString(_:) to distinguish JSON booleans from
numeric NSNumber values before converting them. Inspect the underlying NSNumber
type and return "true"/"false" only for actual booleans; allow numeric 0 and 1
to reach NSNumber.stringValue so enum and const values retain their numeric
labels.
In
`@Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamStore.swift`:
- Around line 174-176: In the history request flow containing the persistence
drain barrier, replace the while loop that repeatedly re-reads
persistenceDrainTask with a single snapshot of the current drain task and await
it once. This ensures historyPage waits only for work queued before the request,
while preserving normal behavior when no drain task is present.
- Around line 436-471: Update decisionForHistory to redact every unkeyed
selection whenever secretIDs is non-empty, while retaining keyed-field
validation and redaction across the full payload rather than only a
single-question fallback. Update QuestionActionArea.composedAnswers to submit
each answer keyed as question.id=answer so the agent receives the field
association correctly.
- Around line 61-64: Remove the test-only production seams: in
WorkstreamStore.swift (lines 61-64), delete activePersistenceDrainCount and make
persistenceDrainTask internal for `@testable` import access; in
WorkstreamPersistence.swift (lines 39-49), remove beforeAppend and its injecting
initializer; and at line 92 remove loadPageCallCount and its stored property.
Update tests to observe internal state directly.
Apply the same fix in
`@Packages/macOS/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/Workstream/WorkstreamStoreTests.swift`
around lines 268 - 269.
In
`@Packages/macOS/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/Workstream/WorkstreamStoreTests.swift`:
- Around line 230-231: Add a requirement immediately after splitting the decoded
cursor in the relevant test, asserting that parts contains at least three
components before accessing parts[2]. Preserve the existing tampered-data
construction and use the test framework’s established `#require` pattern so
malformed cursor formats produce a readable failure instead of trapping.
In `@Resources/Localizable.xcstrings`:
- Around line 207-241: Add translated localizations for all 20 locales supported
by Resources/Localizable.xcstrings to every newly added Feed, Codex, and
permission key group, including feed.question.boolean.no,
feed.question.boolean.yes, feed.form.accepted, feed.form.cancelled, and
feed.form.declined. Preserve the existing English and Japanese values and do not
leave any new key with only en/ja entries or rely on English fallback.
In `@Sources/Feed/FeedCoordinator.swift`:
- Around line 1389-1395: Update FeedJumpResolver.targets(for:) and its
lookup(agent:sessionId:) flow to create a per-call cache of parsed sessions
keyed by agent, so each agent’s *-hook-sessions.json file is read and parsed at
most once while resolving all workstreams. Perform the session-file loading off
the main actor, preserving registeredTargets as the fast path and using the
cached session map only on registry misses.
- Around line 541-558: Update the resolve closure to call the existing
publishMobileChange() method after marking the item resolved, removing its
duplicated waiterLock/mobileRevision increment and workstream.feed.changed
emission. Preserve the existing cancellation flow and rely on
publishMobileChange() as the single publication path.
- Around line 683-689: Update the validation flow around the question input-type
switch to create one shared ISO8601DateFormatter outside the values.allSatisfy
loop, then reuse it for each .dateTime value instead of allocating a formatter
per validation call. Preserve the existing date parsing behavior for other input
types.
- Around line 2233-2244: Update the question-kind routing in CLI/cmux.swift and
Resources/feed-tui/index.ts so rows with kind "boolean" or "form" are treated as
resolvable alongside "question", while preserving the existing handling for
question rows.
In `@Sources/Feed/FeedPanelView.swift`:
- Around line 20-21: Update the localized entries for the six feed permission
mode keys, including feed.permission.mode.persistent, in Localizable.xcstrings
by adding translations for all 18 currently missing supported locales while
preserving the existing en and ja values.
In `@Sources/Feed/FeedPermissionActionPolicy.swift`:
- Around line 145-157: Update codexMCPPersistScopes so it checks each
persistence source in order and selects the first container that actually
contains persist, rather than stopping when object["metadata"] exists without
that key. Preserve the existing precedence of the top-level mcp_persist value,
then metadata, then _meta/meta, and return nil only when none provides persist.
In `@Sources/Panels/AgentSessionProcessStore.swift`:
- Around line 321-338: Replace the detached call to
FeedCoordinator.ingestBlockingWithOutcome in the request-handling flow with an
async waiter that suspends rather than blocks a cooperative-pool thread. Update
the FeedCoordinator decision lifecycle so deliverReply and
invalidateBlockingRequest resume the waiter, while preserving timeout and
cancellation behavior for both blocking and non-blocking requests; do not use
DispatchSemaphore for this async path.
In `@Sources/Panels/CodexAppServerSession.swift`:
- Around line 556-583: Update the user-input task created in the Codex request
handling flow to be stored by rpcID, using the session’s existing task-tracking
collection. During provider teardown, cancel and remove the matching task and
invalidate its associated Feed request, while preserving the existing response
handling and cleanup behavior in the Task body.
In `@Sources/TerminalController.swift`:
- Around line 6105-6114: Update v2FeedInvalidate’s request_id validation to
reject empty or blank String values before calling
FeedCoordinator.shared.invalidateBlockingRequest. Return the existing
invalid_params error response for blank IDs, while preserving the current
invalidation and success behavior for non-blank IDs.
Apply the same fix in `@Sources/TerminalController.swift` around lines 6105 -
6111: Duplicate report of the same empty-request validation defect.
---
Outside diff comments:
In `@Sources/Feed/FeedCoordinator.swift`:
- Around line 2196-2222: Update the item-dictionary construction around
codexCapabilityToolInputJSON and supported_modes to parse toolInputJSON once per
permission item, compute the shared capability set once, and reuse it for
safeToolInputSummary and all FeedPermissionActionPolicy.supports* checks. Apply
the same reuse pattern to the corresponding logic near the additional reported
location.
🪄 Autofix
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: f0b3e078-b5e7-4d6f-8e00-dc605a74410e
📒 Files selected for processing (85)
CLI/FeedEventClassifier.swiftPackages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swiftPackages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileWorkstreamFeedExports.swiftPackages/iOS/CmuxMobileRPC/Tests/CmuxMobileRPCTests/MobileCoreRPCNotificationFeedAuthTests.swiftPackages/iOS/CmuxMobileRPC/Tests/CmuxMobileRPCTests/TransportTestDoubles.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/AgentFeedCacheStore.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/AgentFeedCachedSnapshot.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/AgentFeedMacSnapshot.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+AgentFeed.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+DeeplinkNavigation.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+HiddenMacs.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+SecondaryPromotion.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/SecondaryMacSubscription.swiftPackages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/AgentFeedCacheStoreTests.swiftPackages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ComposerSubmitRoutingTestSupport.swiftPackages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellAgentFeedPagingTests.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileAgentFeedAction.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileAgentFeedAggregation.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileAgentFeedFilter.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileAgentFeedItem.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileAgentFeedItemID.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileAgentFeedMutationState.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileAgentFeedPageAccumulator.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileAgentFeedRefreshTaskCoalescer.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileAgentFeedStatus.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkstreamFeedListItem.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkstreamFeedListResponse.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkstreamFeedPayload.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkstreamFeedStatus.swiftPackages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileAgentFeedTests.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentFeedL10n.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentFeedRow.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentFeedRowChrome.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentFeedStoreView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentFeedView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Debug/AgentFeed/AgentFeedPerformanceProbe.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Debug/AgentFeed/AgentFeedPreviewScenario.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Debug/AgentFeed/AgentFeedPreviewView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileAgentFeedDesign.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobilePrimarySearchCoordinator.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobilePrimaryTab.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobilePrimaryTabScaffold.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/NotificationFeedPreviewView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Resources/Localizable.xcstringsPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListLayoutPreviewView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView.swiftPackages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/Debug/UITestConfig+AgentFeedPreview.swiftPackages/iOS/CmuxMobileSupport/Tests/CmuxMobileSupportTests/UITestConfigTests.swiftPackages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamEvent.swiftPackages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamItem.swiftPackages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamPayload.swiftPackages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamPersistence.swiftPackages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamQuestionPrompt+Parsing.swiftPackages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamStore.swiftPackages/macOS/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/Workstream/WorkstreamEventTests.swiftPackages/macOS/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/Workstream/WorkstreamItemTests.swiftPackages/macOS/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/Workstream/WorkstreamQuestionPromptParsingTests.swiftPackages/macOS/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/Workstream/WorkstreamStoreTests.swiftPackages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Feed/ControlCommandCoordinator+Feed.swiftPackages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/ControlCommandExecutionPolicy.swiftResources/Localizable.xcstringsResources/opencode-plugin.jsSources/CmuxSocketEventMapper.swiftSources/Feed/FeedCoordinator.swiftSources/Feed/FeedPanelView.swiftSources/Feed/FeedPermissionActionPolicy.swiftSources/Mobile/MobileHostService+Capabilities.swiftSources/Mobile/MobileHostService+TicketAuthorization.swiftSources/Panels/AgentSessionProcessStore.swiftSources/Panels/AgentSessionRunningSession.swiftSources/Panels/AgentSessionWebRendererCoordinator.swiftSources/Panels/CodexAppServerSession.swiftSources/TerminalController+ControlFeedContext.swiftSources/TerminalController.swiftcmuxTests/CodexAppServerSessionTests.swiftcmuxTests/FeedCoordinatorTests.swiftcmuxTests/FeedEventClassificationTests.swiftcmuxTests/MobileHostAuthorizationTests.swiftcmuxTests/MobileHostWorkspaceTicketAuthorizationTests.swiftios/cmux-ios.xcodeproj/project.pbxprojios/cmux/Resources/Localizable.xcstringsios/cmuxPackage/Sources/cmuxFeature/CMUXMobileRootScene.swiftios/cmuxUITests/AgentFeedUITests.swift
| private static func isQuestionEventName(_ event: String) -> Bool { | ||
| let normalized = event.unicodeScalars.filter { CharacterSet.alphanumerics.contains($0) } | ||
| .map(String.init) | ||
| .joined() | ||
| .lowercased() | ||
| return [ | ||
| "askuserquestion", | ||
| "askuserconfirmation", | ||
| "askuser", | ||
| "booleanquestion", | ||
| "confirmationrequest", | ||
| "questionasked", | ||
| "questionv2asked", | ||
| "questionrequest", | ||
| "elicitation", | ||
| "elicitationrequest", | ||
| "mcpelicitation", | ||
| "mcpserverelicitationrequest", | ||
| "requestuserinput", | ||
| "userinputrequest", | ||
| "inputrequest", | ||
| "toolrequestuserinput", | ||
| "itemtoolrequestuserinput", | ||
| ].contains(normalized) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
One question-event spelling table, defined twice, already divergent. Both sites normalize an event name by stripping non-alphanumerics and lowercasing, then match a hardcoded list to decide whether the event is a structured user question. The lists disagree, so the same wire value classifies differently depending on which path an event takes.
CLI/FeedEventClassifier.swift#L178-L201: replaceisQuestionEventNamewith a call into the shared spelling table; this copy uniquely containsconfirmationrequestandquestionrequestand omitsquestion.Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamStore.swift#L616-L640: replaceisQuestionEventwith the same shared call; this copy uniquely containsquestionand omitsconfirmationrequestandquestionrequest.
📍 Affects 2 files
CLI/FeedEventClassifier.swift#L178-L201(this comment)Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamStore.swift#L616-L640
🤖 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 `@CLI/FeedEventClassifier.swift` around lines 178 - 201, The question-event
spelling table is duplicated and divergent, causing inconsistent classification.
In CLI/FeedEventClassifier.swift lines 178-201, replace isQuestionEventName with
the shared spelling-table call; in
Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamStore.swift
lines 616-640, replace isQuestionEvent with that same shared call, preserving
the existing normalization behavior and using one canonical list.
| @Test(arguments: ["workstream.feed.action", "workstream.feed.reply"]) | ||
| func testScopedAttachTicketRejectsFeedMutationOutsidePinnedRoute(method: String) throws { | ||
| let ticket = try scopedAttachTicket(workspaceID: "workspace", terminalID: "terminal") | ||
| let request = MobileHostRPCRequest( | ||
| id: "feed-mutation", | ||
| method: method, | ||
| params: [ | ||
| "workspace_id": "other-workspace", | ||
| "surface_id": "other-terminal", | ||
| ], | ||
| auth: MobileHostRPCAuth(attachToken: ticket.authToken, stackAccessToken: nil) | ||
| ) | ||
| let error = MobileHostService.ticketAuthorizationError(ticket: ticket, request: request) | ||
| #expect(error?.code == "forbidden") | ||
| } | ||
| @Test(arguments: ["workstream.feed.action", "workstream.feed.reply"]) | ||
| func testMacScopedAttachTicketAcceptsFeedMutationInAnyWorkspace(method: String) throws { | ||
| let ticket = try scopedAttachTicket(workspaceID: "", terminalID: nil) | ||
| let request = MobileHostRPCRequest( | ||
| id: "feed-mutation", | ||
| method: method, | ||
| params: [ | ||
| "workspace_id": "other-workspace", | ||
| "surface_id": "other-terminal", | ||
| ], | ||
| auth: MobileHostRPCAuth(attachToken: ticket.authToken, stackAccessToken: nil) | ||
| ) | ||
| let error = MobileHostService.ticketAuthorizationError(ticket: ticket, request: request) | ||
| #expect(error == nil) | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
Add the positive scoped case for feed mutations.
The two new tests prove that a scoped ticket rejects a foreign route and that a Mac-wide ticket accepts any route. Neither asserts that a scoped ticket still authorizes workstream.feed.action and workstream.feed.reply on its own pinned workspace and terminal. A check that rejects every feed mutation would pass both tests.
💚 Proposed additional test
`@Test`(arguments: ["workstream.feed.action", "workstream.feed.reply"])
func testScopedAttachTicketAcceptsFeedMutationOnPinnedRoute(method: String) throws {
let ticket = try scopedAttachTicket(workspaceID: "workspace", terminalID: "terminal")
let request = MobileHostRPCRequest(
id: "feed-mutation",
method: method,
params: [
"workspace_id": "workspace",
"surface_id": "terminal",
],
auth: MobileHostRPCAuth(attachToken: ticket.authToken, stackAccessToken: nil)
)
`#expect`(MobileHostService.ticketAuthorizationError(ticket: ticket, request: request) == nil)
}🤖 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 `@cmuxTests/MobileHostAuthorizationTests.swift` around lines 771 - 800, Add a
positive parameterized test alongside
testScopedAttachTicketRejectsFeedMutationOutsidePinnedRoute, covering both
workstream.feed.action and workstream.feed.reply with a scoped ticket whose
workspace_id and surface_id match the ticket’s pinned workspace and terminal;
assert ticketAuthorizationError returns nil.
| MobileHostRPCRequest(id: "agent-feed-list", method: "workstream.feed.list", params: [:], auth: nil), | ||
| MobileHostRPCRequest(id: "agent-feed-action", method: "workstream.feed.action", params: [:], auth: nil), | ||
| MobileHostRPCRequest(id: "agent-feed-reply", method: "workstream.feed.reply", params: [:], auth: nil), |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Inspect scope checks and route resolution for feed mutation methods.
set -euo pipefail
rg -n -C 12 'workstream\.feed\.(action|reply)' --glob '*.swift' Sources Packages | head -200
rg -n -C 8 'ticketAuthorizationError' --glob '*.swift' Sources | head -120Repository: manaflow-ai/cmux
Length of output: 23942
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- ticket authorization implementation ---'
sed -n '1,180p' Sources/Mobile/MobileHostService+TicketAuthorization.swift
printf '%s\n' '--- feed handlers and parameter parsing ---'
rg -n -C 24 'func v2MobileWorkstreamFeed(Action|Reply)|v2MobileWorkstreamFeed(Action|Reply)|workstreamID|workspace_id|surface_id' \
Sources/Mobile Sources/TerminalController.swift Packages/iOS/CmuxMobileShell/Sources --glob '*.swift' | head -500
printf '%s\n' '--- authorization tests around feed requests ---'
sed -n '320,390p' cmuxTests/MobileHostWorkspaceTicketAuthorizationTests.swift
rg -n -C 18 'feedMutations|workstream\.feed\.(action|reply)|ticketCoversTerminalRequest' \
cmuxTests Packages --glob '*.swift' | head -400Repository: manaflow-ai/cmux
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- feed handler definitions ---'
rg -n -l 'v2MobileWorkstreamFeed(Action|Reply)' Sources Packages --glob '*.swift' |
while IFS= read -r file; do
printf '\nFILE: %s\n' "$file"
rg -n -C 35 'v2MobileWorkstreamFeed(Action|Reply)' "$file"
done
printf '%s\n' '--- exact authorization tests ---'
sed -n '335,380p' cmuxTests/MobileHostWorkspaceTicketAuthorizationTests.swift
rg -n -C 30 'feedMutationsPreserveMatchingScopedTicket|feedMutationsReject|workstream\.feed\.(action|reply)' \
Packages/iOS/CmuxMobileRPC/Tests cmuxTests --glob '*.swift'
printf '%s\n' '--- authorization helper remainder and parameter helpers ---'
sed -n '130,280p' Sources/Mobile/MobileHostService+TicketAuthorization.swift
rg -n -C 20 'func stringParamSelection|struct .*ParamSelection|stringParamSelection|containsIgnoredAliasParameters' \
Sources Packages --glob '*.swift'Repository: manaflow-ai/cmux
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- enclosing test setup ---'
sed -n '250,335p' cmuxTests/MobileHostWorkspaceTicketAuthorizationTests.swift
printf '%s\n' '--- all scopedTicket assignments and declarations ---'
rg -n -C 8 'scopedTicket|macWideTicket' cmuxTests/MobileHostWorkspaceTicketAuthorizationTests.swift
printf '%s\n' '--- deterministic authorization probe ---'
python3 - <<'PY'
def ticket_terminal_error(ticket_workspace, ticket_terminal, workspace=None, terminal=None):
if not ticket_workspace.strip():
return None
if workspace is not None and workspace != ticket_workspace.strip():
return "forbidden"
if ticket_terminal and ticket_terminal.strip():
return None if terminal == ticket_terminal.strip() else "forbidden"
return None if workspace == ticket_workspace.strip() else "forbidden"
cases = [
("workspace", None, None, None),
("workspace", None, "workspace", "surface"),
("workspace", None, "other-workspace", "surface"),
("", None, None, None),
]
for case in cases:
print(case, "=>", ticket_terminal_error(*case))
PYRepository: manaflow-ai/cmux
Length of output: 5625
Make the scoped-ticket assertions match authorization behavior.
workstream.feed.action and workstream.feed.reply require workspace_id for a workspace-scoped ticket. With empty parameters, ticketAuthorizationError returns forbidden; only the Mac-wide ticket is authorized. Expect rejection for the scoped ticket, or provide the pinned workspace route.
🤖 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 `@cmuxTests/MobileHostWorkspaceTicketAuthorizationTests.swift` around lines 363
- 365, Update the authorization assertions for the MobileHostRPCRequest entries
in the scoped-ticket test: treat workstream.feed.action and
workstream.feed.reply as forbidden when params are empty, while keeping
agent-feed-list behavior unchanged. Alternatively, include the required
workspace_id in those requests to exercise the authorized pinned-workspace
route.
| XCTAssertGreaterThanOrEqual(frames, 60, value) | ||
| XCTAssertEqual(visibility, 1, value) | ||
| XCTAssertLessThanOrEqual(frameP95, 33, value) | ||
| XCTAssertLessThanOrEqual(visibilityP95, 250, value) | ||
| XCTAssertEqual(frameStalls, 0, value) | ||
| XCTAssertEqual(visibilityStalls, 0, value) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Correctness tests in this layer assert on measured elapsed time. Both sites gate a pass/fail result on runtime measured during the test run, so a correct implementation fails intermittently whenever CI is loaded. The repository test guidelines state: "Do not use fixed sleeps, measured wall-clock assertions, or hard absolute latency ceilings in correctness tests" and "must assert logical invariants rather than measured latency." Keep the measurements as reported instrumentation and move every threshold comparison to a dedicated performance job.
ios/cmuxUITests/AgentFeedUITests.swift#L123-L128: remove theframeP95 <= 33,visibilityP95 <= 250,frameStalls == 0, andvisibilityStalls == 0assertions on values thatAgentFeedPerformanceProbederives fromCADisplayLink.timestampandCACurrentMediaTime(); keep theframes >= 60andvisibility == 1invariants and attach the latency fields for inspection.Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileAgentFeedTests.swift#L303-L332: remove thedoubledTwice < baseline * 8comparison overContinuousClocksamples; keep theoutput.count == min(size, MobileAgentFeedAggregation.maxItemCount)assertion and the printed benchmark line.
📍 Affects 2 files
ios/cmuxUITests/AgentFeedUITests.swift#L123-L128(this comment)Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileAgentFeedTests.swift#L303-L332
🤖 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 `@ios/cmuxUITests/AgentFeedUITests.swift` around lines 123 - 128, The
correctness tests use runtime latency thresholds that can fail under CI load. In
ios/cmuxUITests/AgentFeedUITests.swift lines 123-128, remove the frameP95,
visibilityP95, frameStalls, and visibilityStalls assertions while retaining the
frames and visibility invariants and attaching latency fields for inspection; in
Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileAgentFeedTests.swift
lines 303-332, remove the doubledTwice versus baseline comparison while
retaining the output-count assertion and printed benchmark line. Move threshold
evaluation to the dedicated performance job.
Source: Coding guidelines
| case .persistent: | ||
| return String(localized: "feed.permission.mode.persistent", defaultValue: "remembered") |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Confirm the new Feed localization keys exist with en and ja values.
set -euo pipefail
catalog=$(fd -g 'Localizable.xcstrings' Resources | head -1)
echo "catalog=$catalog"
for key in feed.permission.mode.persistent feed.form.accepted feed.form.declined \
feed.form.cancelled feed.permission.session feed.permission.persistent; do
echo "== $key"
jq --arg k "$key" '.strings[$k].localizations | keys? // "MISSING"' "$catalog"
doneRepository: manaflow-ai/cmux
Length of output: 467
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== localization policy files =="
for f in CLAUDE.md AGENTS.md .github/review-bot-rules/full-internationalization.md; do
if [ -f "$f" ]; then
echo "--- $f"
rg -n -i -C 3 'localiz|locale|supported language|English|Japanese|all supported' "$f" || true
fi
done
echo "== catalog locale and key coverage =="
python3 - <<'PY'
import json
from pathlib import Path
p = Path("Resources/Localizable.xcstrings")
data = json.loads(p.read_text())
keys = [
"feed.permission.mode.persistent",
"feed.form.accepted",
"feed.form.declined",
"feed.form.cancelled",
"feed.permission.session",
"feed.permission.persistent",
]
for key in keys:
entry = data.get("strings", {}).get(key)
print(f"== {key}")
if entry is None:
print("MISSING")
continue
locs = entry.get("localizations", {})
print("locales:", ", ".join(sorted(locs)))
for locale, value in sorted(locs.items()):
unit = value.get("stringUnit", {})
print(f"{locale}: {unit.get('value')!r} ({unit.get('state')})")
PY
echo "== relevant source references =="
rg -n -C 2 'feed\.permission\.mode\.persistent|feed\.form\.(accepted|declined|cancelled)|feed\.permission\.(session|persistent)' Sources/Feed/FeedPanelView.swiftRepository: manaflow-ai/cmux
Length of output: 10385
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
from pathlib import Path
data = json.loads(Path("Resources/Localizable.xcstrings").read_text())
strings = data["strings"]
catalog_locales = sorted({
locale
for entry in strings.values()
for locale in entry.get("localizations", {})
})
keys = [
"feed.permission.mode.persistent",
"feed.form.accepted",
"feed.form.declined",
"feed.form.cancelled",
"feed.permission.session",
"feed.permission.persistent",
]
print("catalog locales:", ", ".join(catalog_locales))
print("catalog locale count:", len(catalog_locales))
for key in keys:
locs = set(strings.get(key, {}).get("localizations", {}))
print(f"{key}: present={','.join(sorted(locs))}; missing={','.join(sorted(set(catalog_locales)-locs)) or '(none)'}")
print("== nearby feed key locale coverage ==")
for key in sorted(k for k in strings if k.startswith("feed.permission") or k.startswith("feed.form")):
locs = sorted(strings[key].get("localizations", {}))
if key in keys or len(locs) > 2:
print(f"{key}: {','.join(locs)}")
PY
echo "== focused diff summary =="
git diff --stat -- Resources/Localizable.xcstrings Sources/Feed/FeedPanelView.swift
git diff --unified=1 -- Resources/Localizable.xcstrings | sed -n '1,220p'Repository: manaflow-ai/cmux
Length of output: 1223
Add translations for all supported locales. These six keys have only en and ja, but Resources/Localizable.xcstrings contains 20 locales. Add the missing 18 translations.
🤖 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/Feed/FeedPanelView.swift` around lines 20 - 21, Update the localized
entries for the six feed permission mode keys, including
feed.permission.mode.persistent, in Localizable.xcstrings by adding translations
for all 18 currently missing supported locales while preserving the existing en
and ja values.
Source: Path instructions
| let timeout: TimeInterval | ||
| if request.isBlocking { | ||
| // Codex defines blocking input as waiting indefinitely. Keep a | ||
| // distant safety deadline while serverRequest/resolved and process | ||
| // exit provide the normal lifecycle-driven cancellation paths. | ||
| timeout = 7 * 24 * 60 * 60 | ||
| } else { | ||
| timeout = min( | ||
| max(Double(request.autoResolutionMilliseconds ?? 120_000) / 1_000, 1), | ||
| 120 | ||
| ) | ||
| } | ||
| let outcome = await Task.detached(priority: .userInitiated) { | ||
| FeedCoordinator.shared.ingestBlockingWithOutcome( | ||
| event: event, | ||
| waitTimeout: timeout | ||
| ) | ||
| }.value |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Do not block a cooperative-pool thread for up to seven days.
FeedCoordinator.ingestBlockingWithOutcome waits on a DispatchSemaphore until the deadline. Line 333 runs it inside Task.detached, so the wait occupies a thread of the Swift concurrency cooperative pool. With request.isBlocking, the deadline is 7 days (line 326); otherwise it is up to 120 seconds. Several concurrent Codex input requests therefore hold several pool threads for a long time, which can starve unrelated async work. Task cancellation also cannot interrupt the semaphore wait, so a cancelled Codex session leaves the thread blocked until the agent process exit path calls invalidateBlockingRequest.
Use an async waiter for the decision instead of a blocking semaphore on this path. One option is a continuation that FeedCoordinator resumes from deliverReply and invalidateBlockingRequest.
As per coding guidelines: "In non-test Swift application and runtime code, flag newly introduced or materially expanded blocking waits such as DispatchSemaphore, semaphore.wait(), DispatchGroup.wait(), and similar thread-blocking waits for async work."
🤖 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/Panels/AgentSessionProcessStore.swift` around lines 321 - 338,
Replace the detached call to FeedCoordinator.ingestBlockingWithOutcome in the
request-handling flow with an async waiter that suspends rather than blocks a
cooperative-pool thread. Update the FeedCoordinator decision lifecycle so
deliverReply and invalidateBlockingRequest resume the waiter, while preserving
timeout and cancellation behavior for both blocking and non-blocking requests;
do not use DispatchSemaphore for this async path.
Source: Coding guidelines
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 (2)
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+HiddenMacs.swift (1)
725-750: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winClear bare feed snapshots for an active tagged target.
foregroundPairingIDusesconnectedMacInstanceTag. If that tag isnil,isActiveMaccan still betruethrough the storedisActivefallback, but the bare ID is not intargetPairingIDs. When a sibling remains,fullyHiddenPhysicalIDsis empty. The device-keyed notification and Agent Feed snapshots then survive after the active pairing is hidden and disconnected.Use
isActiveMacwithforegroundMacDeviceIDto remove both device-keyed snapshots.Proposed fix
- if let foregroundPairingID, targetPairingIDs.contains(foregroundPairingID) { - let identity = MobilePairedMac.pairingIdentity(from: foregroundPairingID) - removeNotificationFeedSnapshot(macDeviceID: identity.macDeviceID) - removeAgentFeedSnapshot(ownerKey: identity.macDeviceID) + if isActiveMac, let foregroundDeviceID = foregroundMacDeviceID { + removeNotificationFeedSnapshot(macDeviceID: foregroundDeviceID) + removeAgentFeedSnapshot(ownerKey: foregroundDeviceID) }Based on learnings: notification-feed snapshots may be applied without a live secondary subscription, and tagged pairing
macInstanceTagmust be derivable from the owner key itself.🤖 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/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+HiddenMacs.swift around lines 725 - 750, Update the foreground snapshot cleanup near foregroundPairingID to use isActiveMac together with foregroundMacDeviceID, rather than relying on foregroundPairingID or targetPairingIDs. When the active Mac is being hidden, remove both notification and Agent Feed snapshots keyed by the bare foreground device ID, including cases where the active pairing has no connectedMacInstanceTag and a sibling remains visible.Source: Learnings
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+SecondaryPromotion.swift (1)
86-90: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winAwait the retired client disconnect.
installControlConnectionis already async, but the disabled-aggregation branch starts an unownedTaskforconnection.client.disconnect(). This lifecycle operation is not stored, cancellable, or tied to a caller-owned operation. Await it afterretire()before returning.Proposed fix
removeFocusedConnection(ifMatching: connection) connection.client.retire() - Task { await connection.client.disconnect() } + await connection.client.disconnect() returnAs per coding guidelines: “Do not create fire-and-forget
Task { ... }work with meaningful lifecycle unless it is stored, cancellable, or tied to a caller-owned operation.”🤖 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/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+SecondaryPromotion.swift around lines 86 - 90, In the disabled-aggregation branch of installControlConnection, replace the unowned Task wrapping connection.client.disconnect() with an awaited disconnect after connection.client.retire() and before returning. Keep the existing capability and focused-connection cleanup unchanged.Source: Coding guidelines
🤖 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
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+HiddenMacs.swift:
- Around line 725-750: Update the foreground snapshot cleanup near
foregroundPairingID to use isActiveMac together with foregroundMacDeviceID,
rather than relying on foregroundPairingID or targetPairingIDs. When the active
Mac is being hidden, remove both notification and Agent Feed snapshots keyed by
the bare foreground device ID, including cases where the active pairing has no
connectedMacInstanceTag and a sibling remains visible.
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+SecondaryPromotion.swift:
- Around line 86-90: In the disabled-aggregation branch of
installControlConnection, replace the unowned Task wrapping
connection.client.disconnect() with an awaited disconnect after
connection.client.retire() and before returning. Keep the existing capability
and focused-connection cleanup unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 47c838db-b005-49cb-89c5-ef9800cc5321
📒 Files selected for processing (13)
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+HiddenMacs.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+SecondaryPromotion.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/SecondaryMacSubscription.swiftPackages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ComposerSubmitRoutingTestSupport.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/NotificationFeedPreviewView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Resources/Localizable.xcstringsPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView.swiftios/cmux-ios.xcodeproj/project.pbxprojios/cmux/Resources/Localizable.xcstringsios/cmuxPackage/Sources/cmuxFeature/CMUXMobileRootScene.swift
💤 Files with no reviewable changes (1)
- Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.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)
Sources/Panels/AgentSessionProcessStore.swift (2)
721-732: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winReject selections that violate the accepted MCP schema.
mcpElicitationIsSupportedaccepts enum and numeric-bound constraints.mcpValuefalls through totypedMCPValuewhen a selection does not match an enum value. It also does not enforceminimumormaximum.A stale or injected selection such as
target=linuxorcount=99can reach the MCP server despite the accepted schema. Validate property names, enum values, and numeric bounds. If validation fails, cancel the elicitation instead of serializing invalid content.🤖 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/Panels/AgentSessionProcessStore.swift` around lines 721 - 732, The MCP value handling around mcpValue must enforce the accepted schema before serialization: validate property names, reject values outside allowed enum selections, and enforce numeric minimum and maximum bounds. When validation fails, cancel the elicitation rather than falling through to typedMCPValue; preserve valid indexed, named, and schema-compatible values.
259-308: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy liftMove Codex request normalization off
MainActor.
CodexAppServerSessionandAgentSessionProcessStoreare@MainActor. Server-request input is not covered by the 64 KiB queued-text limit. Decode and normalize provider-controlled JSON in a non-main-actor helper. PassDataor aSendableDTO across the actor boundary. Keep only session and UI state changes onMainActor.🤖 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/Panels/AgentSessionProcessStore.swift` around lines 259 - 308, Move request JSON decoding and normalization currently performed in the MainActor-isolated request handling flow around CodexAppServerSession and AgentSessionProcessStore into a non-MainActor, Sendable helper. Pass the resulting Data or Sendable DTO across the actor boundary, keeping only session/UI state mutations and event coordination on MainActor; ensure provider-controlled input remains outside the queued-text limit.Source: Coding guidelines
🤖 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 `@cmuxTests/CodexAppServerSessionTests.swift`:
- Around line 1378-1382: Replace the fixed Task.yield loop after
session.consumeStdout in the approval test with an explicit completion signal
from userInputHandler. Await that signal before inspecting receivedRequest or
sentLines, using a continuation, AsyncStream, or deadline-bounded predicate so
assertions only run after the response task completes.
---
Outside diff comments:
In `@Sources/Panels/AgentSessionProcessStore.swift`:
- Around line 721-732: The MCP value handling around mcpValue must enforce the
accepted schema before serialization: validate property names, reject values
outside allowed enum selections, and enforce numeric minimum and maximum bounds.
When validation fails, cancel the elicitation rather than falling through to
typedMCPValue; preserve valid indexed, named, and schema-compatible values.
- Around line 259-308: Move request JSON decoding and normalization currently
performed in the MainActor-isolated request handling flow around
CodexAppServerSession and AgentSessionProcessStore into a non-MainActor,
Sendable helper. Pass the resulting Data or Sendable DTO across the actor
boundary, keeping only session/UI state mutations and event coordination on
MainActor; ensure provider-controlled input remains outside the queued-text
limit.
🪄 Autofix
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: e7f94db2-8a82-41d5-9f89-61bc4ecfffa1
📒 Files selected for processing (5)
CLI/CodexTeamsApprovalBridge.swiftSources/Panels/AgentSessionProcessStore.swiftSources/Panels/CodexAppServerSession.swiftcmux.xcodeproj/project.pbxprojcmuxTests/CodexAppServerSessionTests.swift
| session.consumeStdout( | ||
| #"{"id":"approval-1","method":"item/commandExecution/requestApproval","params":{"threadId":"thread-1","command":"swift test","availableDecisions":["accept","decline"]}}"# | ||
| + "\n" | ||
| ) | ||
| for _ in 0..<3 { await Task.yield() } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Replace the fixed yields with a completion signal.
Line 1382 does not guarantee that userInputHandler has run. The test can read receivedRequest or sentLines before the response task completes.
Signal from userInputHandler and await that signal before the assertions. Use a continuation, an AsyncStream, or a deadline-bounded predicate.
🤖 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 `@cmuxTests/CodexAppServerSessionTests.swift` around lines 1378 - 1382, Replace
the fixed Task.yield loop after session.consumeStdout in the approval test with
an explicit completion signal from userInputHandler. Await that signal before
inspecting receivedRequest or sentLines, using a continuation, AsyncStream, or
deadline-bounded predicate so assertions only run after the response task
completes.
Source: Coding guidelines
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+SecondaryPromotion.swift:
- Line 650: In the reuse path following removeAgentFeedSnapshot(ownerKey:
previousForegroundID), also schedule scheduleSecondaryAgentFeedRefresh using
subscription.ownerKey.pairingID and the existing client. Keep the existing
scheduleSecondaryNotificationFeedRefresh call and ensure the demoted pairing’s
Agent Feed is refreshed immediately after its snapshot is removed.
In
`@Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamQuestionPrompt`+Parsing.swift:
- Around line 169-179: Update the rawDefault decoding near rawDefaultValue to
distinguish bridged JSON booleans from numbers before converting values. Apply
the existing CFBooleanGetTypeID-based check used for option values around the
NSNumber handling, preserving true/false conversion only for actual CFBoolean
values and using NSNumber.stringValue for numeric defaults.
In `@Sources/Panels/AgentSessionProcessStore.swift`:
- Around line 797-800: Remove the invalid optional chaining from the schema type
lookup in the surrounding conversion function: after the guard let schema
binding, access the type with schema["type"] instead of schema?["type"],
preserving the existing lowercasing and switch behavior.
🪄 Autofix
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: 722899de-4465-497e-acd0-f39873b09d79
📒 Files selected for processing (17)
CLI/FeedEventClassifier.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/AgentFeedCacheStore.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+AgentFeed.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+HiddenMacs.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+SecondaryPromotion.swiftPackages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/AgentFeedCacheStoreTests.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileAgentFeedMutationState.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentFeedRow.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Resources/Localizable.xcstringsPackages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamQuestionPrompt+Parsing.swiftPackages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamStore.swiftSources/Feed/FeedCoordinator.swiftSources/Feed/FeedPermissionActionPolicy.swiftSources/Panels/AgentSessionProcessStore.swiftSources/Panels/CodexAppServerSession.swiftSources/TerminalController.swiftcmuxTests/CodexAppServerSessionTests.swift
| removeNotificationFeedSnapshot( | ||
| macDeviceID: previousForegroundID | ||
| ) | ||
| removeAgentFeedSnapshot(ownerKey: previousForegroundID) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Refresh the demoted pairing’s Agent Feed after removing its bare snapshot.
Line 650 deletes the old foreground Agent Feed snapshot. The reuse path only schedules scheduleSecondaryNotificationFeedRefresh, so the demoted Mac has no pairing-keyed Agent Feed snapshot until an unrelated refresh occurs. Schedule scheduleSecondaryAgentFeedRefresh(ownerKey:client:) with subscription.ownerKey.pairingID in that path.
Proposed fix
self.scheduleSecondaryNotificationFeedRefresh(
macDeviceID: subscription.ownerKey.pairingID,
client: subscription.client,
displayName: subscription.displayName
)
+self.scheduleSecondaryAgentFeedRefresh(
+ ownerKey: subscription.ownerKey.pairingID,
+ client: subscription.client
+)🤖 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/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+SecondaryPromotion.swift
at line 650, In the reuse path following removeAgentFeedSnapshot(ownerKey:
previousForegroundID), also schedule scheduleSecondaryAgentFeedRefresh using
subscription.ownerKey.pairingID and the existing client. Keep the existing
scheduleSecondaryNotificationFeedRefresh call and ensure the demoted pairing’s
Agent Feed is refreshed immediately after its snapshot is removed.
| let rawDefault: Any? = dictionary["default"] ?? dictionary["default_value"] | ||
| let rawDefaultValue: String? | ||
| if let value = rawDefault as? String { | ||
| rawDefaultValue = value | ||
| } else if let value = rawDefault as? Bool { | ||
| rawDefaultValue = value ? "true" : "false" | ||
| } else if let value = rawDefault as? NSNumber { | ||
| rawDefaultValue = value.stringValue | ||
| } else { | ||
| rawDefaultValue = nil | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Check NSNumber before Bool so numeric defaults survive.
JSONSerialization bridges JSON integers to NSNumber. On the Objective-C bridge, an NSNumber holding 0 or 1 also satisfies as? Bool. Line 173 therefore runs before line 175, and {"type":"integer","default":1} produces rawDefaultValue == "true".
For inputType == .boolean the new mapping at lines 181-187 absorbs this, so boolean fields are correct. Numeric fields are not: AgentFeedRow.fieldValueIsValid parses the default with Double("true"), which returns nil and blocks form submission, and formSelectionsForSubmission would emit id=true.
Line 239 already solves this discrimination for option values. Apply the same rule to the default decoder.
🐛 Proposed fix for the boolean/number precedence
let rawDefault: Any? = dictionary["default"] ?? dictionary["default_value"]
let rawDefaultValue: String?
if let value = rawDefault as? String {
rawDefaultValue = value
- } else if let value = rawDefault as? Bool {
- rawDefaultValue = value ? "true" : "false"
} else if let value = rawDefault as? NSNumber {
- rawDefaultValue = value.stringValue
+ // JSONSerialization bridges booleans and numbers through
+ // NSNumber, and 0/1 satisfy `as? Bool`. Inspect the underlying
+ // type so numeric defaults keep their numeric text.
+ rawDefaultValue = CFGetTypeID(value as CFTypeRef) == CFBooleanGetTypeID()
+ ? (value.boolValue ? "true" : "false")
+ : value.stringValue
+ } else if let value = rawDefault as? Bool {
+ rawDefaultValue = value ? "true" : "false"
} else {
rawDefaultValue = nil
}Consider using the same CFBooleanGetTypeID() check at line 239. Int8-backed NSNumber values also report objCType == "c", so the CoreFoundation type id is the stricter test.
Based on learnings: "when guarding against boolean-typed JSON values bridged through [String: Any] … do NOT use value is Bool — JSON integers 0 and 1 round-trip as __NSCFNumber objects that satisfy as? Bool … The correct guard is a CoreFoundation type-id check: CFGetTypeID(value as CFTypeRef) == CFBooleanGetTypeID()."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let rawDefault: Any? = dictionary["default"] ?? dictionary["default_value"] | |
| let rawDefaultValue: String? | |
| if let value = rawDefault as? String { | |
| rawDefaultValue = value | |
| } else if let value = rawDefault as? Bool { | |
| rawDefaultValue = value ? "true" : "false" | |
| } else if let value = rawDefault as? NSNumber { | |
| rawDefaultValue = value.stringValue | |
| } else { | |
| rawDefaultValue = nil | |
| } | |
| let rawDefault: Any? = dictionary["default"] ?? dictionary["default_value"] | |
| let rawDefaultValue: String? | |
| if let value = rawDefault as? String { | |
| rawDefaultValue = value | |
| } else if let value = rawDefault as? NSNumber { | |
| // JSONSerialization bridges booleans and numbers through | |
| // NSNumber, and 0/1 satisfy `as? Bool`. Inspect the underlying | |
| // type so numeric defaults keep their numeric text. | |
| rawDefaultValue = CFGetTypeID(value as CFTypeRef) == CFBooleanGetTypeID() | |
| ? (value.boolValue ? "true" : "false") | |
| : value.stringValue | |
| } else if let value = rawDefault as? Bool { | |
| rawDefaultValue = value ? "true" : "false" | |
| } else { | |
| rawDefaultValue = nil | |
| } |
🤖 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/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamQuestionPrompt`+Parsing.swift
around lines 169 - 179, Update the rawDefault decoding near rawDefaultValue to
distinguish bridged JSON booleans from numbers before converting values. Apply
the existing CFBooleanGetTypeID-based check used for option values around the
NSNumber handling, preserving true/false conversion only for actual CFBoolean
values and using NSNumber.stringValue for numeric defaults.
Source: Learnings
9f8abe7 to
713f9a5
Compare
713f9a5 to
d135f50
Compare
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)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Debug/AgentFeed/AgentFeedPreviewView.swift (1)
128-136: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winComplete the reply acknowledgment state transition.
Acknowledge replyonly clearsdraftsandmutationStates. It does not change the completed-turn item. The item remains in the.needsInputprojection, so the reply composer appears again after acknowledgment. Update the item through the preview reconciliation path, or remove it from the needs-input projection, before clearing the mutation state.🤖 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/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Debug/AgentFeed/AgentFeedPreviewView.swift` around lines 128 - 136, Update the Acknowledge reply action in AgentFeedPreviewView so it reconciles the completed-turn item out of the needs-input projection before clearing drafts and mutationStates. Use the existing preview reconciliation path or equivalent item update, ensuring the reply composer does not reappear after acknowledgment.
🤖 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 `@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentFeedRow.swift`:
- Around line 569-587: Update AgentFeedContext.body’s “No response. The agent
stopped waiting.” fallback so it is shown only when turnCompletionActionArea
will not render the fallback; suppress the header message for completed
needs-input turns with !interactionsEnabled and no lastAssistantMessage, while
preserving the existing behavior for other cases.
---
Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Debug/AgentFeed/AgentFeedPreviewView.swift`:
- Around line 128-136: Update the Acknowledge reply action in
AgentFeedPreviewView so it reconciles the completed-turn item out of the
needs-input projection before clearing drafts and mutationStates. Use the
existing preview reconciliation path or equivalent item update, ensuring the
reply composer does not reappear after acknowledgment.
🪄 Autofix
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: 9f8a65d1-2ca7-45d9-a024-3001296b2e1f
📒 Files selected for processing (11)
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkstreamFeedListItem.swiftPackages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileAgentFeedTests.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentFeedRow.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentFeedView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Debug/AgentFeed/AgentFeedPreviewScenario.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Debug/AgentFeed/AgentFeedPreviewView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Resources/Localizable.xcstringsPackages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/Workstream/WorkstreamStore.swiftPackages/macOS/CMUXAgentLaunch/Tests/CMUXAgentLaunchTests/Workstream/WorkstreamStoreTests.swiftSources/Feed/FeedCoordinator.swiftcmuxTests/FeedCoordinatorTests.swift
Summary
Testing
swift test --package-path Packages/macOS/CMUXAgentLaunch(316 tests)swift test --package-path Packages/iOS/CmuxMobileShellModel(297 tests)Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Adds an iOS Agent Feed tab that aggregates coding‑agent activity across paired Macs with inline approvals, structured questions (booleans and MCP forms), and replyable turn completions. Previously iOS had no agent feed; now it shows revisioned, paged cross‑Mac events with an offline cache and live reconciliation, and fails closed on stale navigation or mismatched scopes.
workstream.feed.v1: Macs must implementworkstream.feed.list(revisioned pages withnext_cursor/has_more), publishworkstream.feed.changed, and acceptfeed.invalidate; older hosts show “requires update.”workstream.feed.listuses account authorization;workstream.feed.actionandworkstream.feed.replyrequire a scoped attach ticket matchingworkspace_id/surface_idand reject mismatches.agentFeedorigin; EN/JA localization; deterministic preview viaCMUX_UITEST_AGENT_FEED_PREVIEW=1; performance probe; comprehensive UI/model and UITest coverage.Written for commit 354df52. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes