Repository navigation
Add model selection lab to the New Task composer - #8800
Conversation
Adds a curated per-provider model catalog (Claude, Codex, OpenCode) with opt-in model-flag injection into template commands, and five UX variants for picking the model in the New Task sheet (combined agent menu, model row, trailing chip, pill strip, context row), switchable at runtime from the DEBUG-only CMUX Labs 'New Task Model Lab'. No model selected keeps template commands byte-for-byte verbatim; release builds stay off. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
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:
📝 WalkthroughWalkthroughAdds curated agent-model support to task composition, persists selected model IDs through drafts and submissions, and introduces debug-configurable iOS picker layouts with reusable model-selection controls and updated task composer wiring. ChangesTask composer model selection
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant TaskComposerPromptCard
participant TaskComposerSheet
participant MobileTaskAgentProvider
participant MobileTaskSubmissionSnapshot
User->>TaskComposerPromptCard: Select agent model
TaskComposerPromptCard->>TaskComposerSheet: Pass model ID
TaskComposerSheet->>MobileTaskAgentProvider: Validate model for template
TaskComposerSheet->>MobileTaskSubmissionSnapshot: Save model ID
MobileTaskSubmissionSnapshot->>MobileTaskAgentProvider: Compose command with model flag
Possibly related PRs
🚥 Pre-merge checks | ✅ 23 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (23 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
⚔️ Resolve merge conflicts 💡
🧪 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 |
Greptile SummaryThis PR adds a model-selection feature to the iOS New Task composer, with five UX variants (combined menu, model row, trailing chip, pill strip, context row) switchable at runtime from a DEBUG-only lab in Settings. In production builds, the feature is fully off via
Confidence Score: 5/5Safe to merge. The model-selection feature is fully off in release builds, all command mutation is guarded by catalog validation, and draft/snapshot round-trips are backward compatible. The core injection path is well-tested and byte-for-byte preserves commands without an explicit model selection. All new strings are localized for both supported locales. The production data model changes decode cleanly from legacy drafts. The only new observations are debug-only concerns that do not affect shipping behavior. TaskComposerAgentMenuContent.swift and TaskComposerSheet.swift — specifically how the combined-menu variant fires two sequential updateSubmissionRequest calls per agent+model tap, and whether that stays safe if reconcileCurrentRequest gains non-idempotent side effects in the future. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User opens New Task sheet] --> B{Draft has modelID?}
B -- Yes --> C[Validate model against current template command]
B -- No --> D[selectedModelID = nil]
C -- Valid --> E[selectedModelID = draft.modelID]
C -- Invalid / wrong provider --> D
E --> F[User picks agent or model]
D --> F
F --> G{Action type}
G -- selectTemplate --> H[selectedModelID = nil, updateSubmissionRequest]
G -- selectModel --> I{Model valid for current template?}
I -- Yes --> J[selectedModelID = model.id, updateSubmissionRequest]
I -- No --> K[no-op]
H --> L[buildSubmissionSnapshot]
J --> L
D --> L
L --> M{selectedModelID != nil?}
M -- Yes --> N[commandApplying inserts flag after binary token]
M -- No --> O[command unchanged byte-for-byte]
N --> P[MobileTaskSubmissionSnapshot.composition]
O --> P
P --> Q[Submit task to Mac]
Reviews (2): Last reviewed commit: "Add Codex-style composer layout to the N..." | Re-trigger Greptile |
| public enum MobileTaskAgentModelCatalog { | ||
| /// Lexically detects a provider from the command's first | ||
| /// whitespace-delimited token. | ||
| /// |
There was a problem hiding this comment.
Caseless enum used as static namespace
MobileTaskAgentModelCatalog is a caseless enum whose entire API consists of public static functions — exactly the static-namespace shape the cmux-no-ambient-global-state rule flags. The inline /// lint:allow namespace-type comment is a doc comment, not a recognized suppression directive. The preferred shape is a struct that can be constructed and injected (e.g. public struct MobileTaskAgentModelCatalog { public init() {} … } with instance methods), which also opens a test-seam for custom catalogs without exposing implementation details to production Sources/.
Rule Used: Flag new ambient global state in production Swift:... (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in 656cf07: dissolved the caseless enum entirely. Detection is now MobileTaskAgentProvider.init?(command:), the curated lists are provider.models, and flag application is an instance method, so there is no namespace type left.
— Claude Code
| } | ||
|
|
||
| /// Inserts a provider-specific model flag immediately after the command's | ||
| /// first token. | ||
| /// | ||
| /// A `nil` model identifier or unsupported command is returned unchanged. | ||
| /// Everything after the first token remains byte-for-byte identical. | ||
| /// - Parameters: | ||
| /// - modelID: CLI model identifier to single-quote for the flag value. | ||
| /// - command: User-authored task-template command. | ||
| /// - Returns: The flagged command, or the original command when not applicable. | ||
| public static func commandApplying(modelID: String?, to command: String) -> String { | ||
| guard let modelID, |
There was a problem hiding this comment.
Double-flag injection when template already specifies a model
commandApplying always inserts the flag immediately after the binary token with no guard against an existing --model / -m flag in the template command. A user whose template is claude --model claude-sonnet-4-5 --verbose and who then picks claude-opus-4-8 in the lab will produce claude --model 'claude-opus-4-8' --model claude-sonnet-4-5 --verbose. Claude CLI's flag-precedence behaviour for duplicate --model is not guaranteed, so the selected model may be silently ignored. Consider scanning command[tokenRange.upperBound...] for an existing flag matching the provider's flag spelling before injecting, and either replacing it in place or returning the command unchanged with the new model applied to the flag that is already there.
There was a problem hiding this comment.
Fixed in 656cf07: command(applying:to:) now replaces the value of an existing --model / -m / --model= token in place (scanning stops at a standalone --), so the explicit selection always wins and no duplicate flag is emitted. Covered by new unit tests.
— Claude Code
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/TaskComposer/TaskComposerAgentMenu.swift`:
- Around line 138-170: Replace the two-step selection calls in the
template/model menu with one shared combined action, such as
selectTemplateAndModel(templateID:modelID:), covering both the default nil-model
case and specific models. Implement the combined action alongside the existing
selectTemplate and selectModel logic so both mutations occur within a single
updateSubmissionRequest reconciliation/persistence block, then update each menu
button to invoke only that action while preserving selection state and
accessibility behavior.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift`:
- Around line 44-47: The model-name lookup and accessibility configuration are
duplicated across the model-picker variants. In
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift#L44-L47
and `#L35-L40`,
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelContextRow.swift#L41-L53,
and
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelRow.swift#L49-L61,
add shared helpers for resolving the selected MobileTaskAgentModel display name
and applying the accessibility label/value/hint, then replace each local
implementation with those helpers.
🪄 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: 417109d5-34bf-490b-a0b9-58b77ab9ce2c
📒 Files selected for processing (27)
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentModelCatalog.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskCommandComposer.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskComposerDraft.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskSubmissionSnapshot.swiftPackages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskAgentModelCatalogTests.swiftPackages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskCommandComposerTests.swiftPackages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskComposerDraftTests.swiftPackages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskSubmissionSnapshotTests.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenu.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuActions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuValue.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerContextSection.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelContextRow.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelMenuContent.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelPickerLabView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelPickerVariant.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelPillStrip.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelRow.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPromptCard.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DraftState.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swiftPackages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swiftios/cmux/Resources/Localizable.xcstrings
New minimal layout (lab-switchable, DEBUG default): full-bleed prompt canvas titled by the working directory, back chevron, and a bottom control bar with a + options sheet (name, Mac, directory), agent pill, model pill, and a circular submit button. Classic card layout stays available via CMUX Labs and renders unchanged; release builds keep classic. Adds GPT-5.6 Luna to the Codex model catalog. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Round 2 (52a4d22): added a Codex-style composer layout per owner request — full-bleed prompt canvas titled by the working directory, back chevron, bottom control bar with + options sheet (workspace name / Mac / directory), agent pill, model pill, and circular submit. Lab-switchable in Settings → CMUX Labs → New Task Model Lab (Layout: Classic / Composer); Composer is the DEBUG default, release stays Classic. Classic renders unchanged. Also added GPT-5.6 Luna to the Codex model catalog. 188 model tests pass; both layouts sim-verified. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/TaskComposer/TaskComposerSheet.swift`:
- Around line 371-406: Extract the duplicated injected-override/store-fallback
logic from the top-level directory-picker sheet and minimalOptionsSheet into
shared private helpers such as resolvedSearchTaskDirectories(query:) and
resolvedListTaskDirectories(path:offset:). Replace both existing searchMac and
listMac closure implementations with calls to those helpers, preserving the
current arguments and fallback behavior.
🪄 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: e2d2df0e-1d19-4f33-af03-08ed6d91e905
📒 Files selected for processing (14)
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentModelCatalog.swiftPackages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskAgentModelCatalogTests.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenu.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuContent.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerFailureRecoveryContent.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerLayoutStyle.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMinimalLayout.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelPickerLabView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerOptionsSheet.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPrimaryAction.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swiftPackages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swiftios/cmux/Resources/Localizable.xcstrings
- Dissolve the MobileTaskAgentModelCatalog static namespace into MobileTaskAgentProvider (detection init, models, model(id:), command(applying:to:)) per the no-static-namespace policy. - Replace an existing --model/-m/--model= value in place instead of injecting a duplicate flag that the template's own value would override; stop scanning at the -- end-of-options token. - Pin the task-composer accessibility preview to the classic layout and Off variant on fresh installs so the XCUITest suite keeps a stable element tree; CMUX_UITEST_TASK_COMPOSER_LAYOUT/_MODEL_VARIANT opt in. - Give each lab variant exactly one placement in the composer layout: combined stays in the agent submenu, contextRow stays in Task Options, the rest collapse to the standalone bottom-bar pill. - Gate the composer submit button on blocking completed-operation recovery, matching the classic layout. - Make combined menu taps a single atomic template+model mutation. - Share the model display-name fallback and accessibility triple; share the directory search/list fallback closures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift`:
- Around line 95-103: Update the model-flag handling in the token loop of
MobileTaskAgentProvider so when the token after a recognized model flag is `--`,
insert the selected quotedID before the boundary instead of replacing or
removing `--`; preserve existing behavior for normal values and missing values,
and add a regression test covering `claude --model -- "$CMUX_TASK_PROMPT"`.
🪄 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: 8226f0f2-a84f-4f6b-98ec-1714db293d53
📒 Files selected for processing (17)
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskCommandComposer.swiftPackages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskAgentProviderTests.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenu.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuActions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuContent.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMinimalLayout.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelContextRow.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelDisplay.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelRow.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPromptCard.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DraftState.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swiftPackages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift
- Quote-aware token scanning in command(applying:to:): flag text inside single/double-quoted arguments is one opaque token, so quoted mentions of --model are never rewritten; every real model flag before -- is replaced (not just the first); a flag directly before -- gets its value supplied in place. - Gate selectedModel on the rendered picker variant so a draft-restored model cannot ride into snapshots or submissions while the picker is Off; the stored selection survives for when a variant is re-enabled. - Show the selected model in the composer layout's combined variant (agent pill title gains ' · <model>') and add visible checkmarks to the combined submenu rows. - Extend compact composer controls (+, submit, pills, chip, row, pill strip) to 44pt activation targets without changing their visuals. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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. |
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/TaskComposer/TaskComposerSheet.swift (1)
677-677: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDo not clear the retained model while the picker is off.
Line 677 assigns from
selectedModel, which is deliberatelynilwhenever the picker variant is.off. Any template refresh therefore erasesselectedModelID, so re-enabling the picker cannot restore the prior valid choice. Validate the ID directly against the selected template’s provider instead, and add an off → refresh → on regression test.Based on PR objectives, the hidden selection must remain available when the picker is re-enabled.
🤖 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/TaskComposer/TaskComposerSheet.swift` at line 677, Update the template refresh logic around selectedModelID so it does not overwrite the retained ID when the model picker variant is .off. Validate the existing ID directly against the selected template’s provider rather than deriving it from selectedModel, while preserving valid selections and clearing only invalid IDs. Add a regression test covering off → refresh → on and confirming the prior model selection is restored.
🤖 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/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift`:
- Around line 104-120: Update the model-flag detection in the command-rewrite
method containing modelFlagSpellings and tokenRange to decode shell-quoted token
values before comparing them with flag spellings, so tokens such as "--model"
are recognized. Ensure replacement logic removes or updates the existing
provider-specific model option rather than prepending a duplicate, and add a
regression case for `claude "--model" old -- "$CMUX_TASK_PROMPT"`.
---
Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift`:
- Line 677: Update the template refresh logic around selectedModelID so it does
not overwrite the retained ID when the model picker variant is .off. Validate
the existing ID directly against the selected template’s provider rather than
deriving it from selectedModel, while preserving valid selections and clearing
only invalid IDs. Add a regression test covering off → refresh → on and
confirming the prior model selection is restored.
🪄 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: 58ee3f46-2f33-4bda-9d19-486172510010
📒 Files selected for processing (9)
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swiftPackages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskAgentProviderTests.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuContent.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMinimalLayout.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelPillStrip.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelRow.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift
- Stop the model-flag scan at the first simple command's end: a newline separator or a token carrying an unquoted ;, |, or &. A compound template like 'claude "$CMUX_TASK_PROMPT"; formatter --model compact' now inserts Claude's flag after the first token and leaves the later command untouched. - Route every submission through effectiveSubmissionSnapshot: while the picker variant is Off, a hidden model captured by the cached restored request (or an adopted recovery request) is stripped and the command recomposed with the same operation identifier, closing the untouched- draft bypass of the selectedModel gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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/TaskComposer/TaskComposerSheet.swift (1)
674-678: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve the hidden model selection during template refresh.
At Line 678,
.offmakesselectedModelnil by design, so every refresh clearsselectedModelID. Validate the stored ID directly against the refreshed template instead.Proposed fix
- selectedModelID = selectedModel?.id + selectedModelID = selectedModelID.flatMap { modelID in + selectedTemplate.flatMap { template in + MobileTaskAgentProvider(command: template.command)? + .model(id: modelID)? + .id + } + }🤖 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/TaskComposer/TaskComposerSheet.swift` around lines 674 - 678, Update the template refresh logic in TaskComposerSheet so selectedModelID is not cleared when selectedModel is nil, including the intentional `.off` state. Validate the existing stored model ID directly against the refreshed selected template, and only update it when that template no longer contains the ID.
🤖 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/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift`:
- Around line 104-120: Update the model-flag scanning logic around tokenRange
and containsUnquotedCommandBoundary to split an unquoted shell operator suffix
from an attached value, rather than treating the whole token as only a boundary.
Replace the existing model value for both separated and equals forms while
preserving the operator and stopping at the first simple-command boundary. Add
regressions for legacy; and legacy&& cases, including --model=legacy;.
---
Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift`:
- Around line 674-678: Update the template refresh logic in TaskComposerSheet so
selectedModelID is not cleared when selectedModel is nil, including the
intentional `.off` state. Validate the existing stored model ID directly against
the refreshed selected template, and only update it when that template no longer
contains the ID.
🪄 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: 2954d896-0503-4d42-86f4-a05fb5d28dd3
📒 Files selected for processing (4)
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swiftPackages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskAgentProviderTests.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift
- Build the composer options sheet lazily: MinimalLayout now takes a deferred builder, so directory-candidate construction (a workspace walk) runs only when Task Options is presented, not on every prompt keystroke's body rebuild. - Reconcile a hidden model at the submission boundary by marking the request dirty instead of post-hoc snapshot surgery: resolution runs through makeSubmissionSnapshot (whose selectedModel gate strips the model) and MobileTaskSubmissionIdentity mints a fresh operation ID for the changed bytes, keeping retries idempotent. - Process a model flag attached to a command separator: --model=old;, --model old;, and --model; are rewritten before scanning stops, so a stale value can no longer override the selection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Reconcile a hidden model at BOTH request-resolution boundaries via one shared resolver: when the Off picker hides a model a clean cached request still carries, resolution is forced through the selectedModel gate and the identity mints a fresh operation ID, so a persisted draft can never pair model-less bytes with an ID previously bound to model-bearing bytes. Replaces the submit-only proxy check. - Treat redirection operators as part of the simple command: & adjacent to > (2>&1, >&2, &>file) and | preceded by > (>|file) no longer end the flag scan, so a stale --model after a redirection is still replaced. Control operators (;, |, &, &&, ||) still end it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Stop the model-flag scan at an unquoted word-initial #: a commented flag is never rewritten, and the selection is inserted after the first token instead of being silently swallowed by a comment edit. - Do not reuse a draft's operation ID (or restore its completed- operation recovery) when the draft's model no longer survives curated-list validation; the resulting default-model command gets a fresh idempotency key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cker # Conflicts: # Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swift # Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift # Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift
Dogfood feedback: + implied adding something; the button configures the task (name, Mac, directory), so it now shows slider.horizontal.3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dogfood feedback: switching the agent or model to a longer title left the pill label clipped for the length of the resize animation. The pill content now uses fixedSize so the capsule adopts the new intrinsic width immediately, and the label subtree is identity-keyed on the title so it swaps instead of animating through stale-width frames. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ipping Dogfood feedback: the combined lab variant hid the standalone model pill (models lived only in the agent submenu), which read as the model picker disappearing. The composer layout now has one canonical model treatment: a dedicated pill beside the agent pill for every non-Off variant; the agent menu is forced plain and the options sheet never repeats the contextRow, so the pill stays the single entry point. The label clipping on longer titles survived the fixedSize fix because the identity key sat on the label content while the UIKit menu button still animated its frame. The .id now keys the whole Menu, so a title change swaps the button instead of animating through stale bounds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dogfood request: pills should dissolve toward the neighboring options and submit buttons instead of clipping at the scroller bounds. iOS 26 uses the native soft scroll edge effect (progressive blur + fade); earlier systems approximate it with a 14pt alpha-mask fade per edge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cker # Conflicts: # ios/cmux/Resources/Localizable.xcstrings
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. |
Main's typed MacPairingKey re-key changed the secondary-subscription registry key from a device-id string to the full pairing key. The attachment and model-discovery capability checks now resolve through a shared controlSubscriptionMatching helper that keeps the old semantics: exact pairing when a tag is given, any same-device pairing otherwise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dogfood feedback: the scroll edge effect never rendered because the buttons sat NEXT TO the scroller, so no content ever passed beneath an edge. The pill scroller now spans the bar with the attachment/options buttons and the submit button living in its leading/trailing safe-area insets: pills genuinely scroll under them, which is what activates the native iOS 26 soft scroll edge effect (progressive blur + fade). Pre-26 keeps an opaque button background as the fallback occlusion. Also stop the prompt editor from yanking long text back down while scrolling up: interactive keyboard dismissal resized the editor every drag frame and UITextView re-scrolled to the caret each time; dismissal is now immediate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ction SwiftUI's scrollEdgeEffectStyle only styles effects the system already owns (bars/glass), so pills merely underlapped the buttons. The bar row is now a thin UIKit host: a horizontal UIScrollView spans the bar, the button clusters float above it, and on iOS 26 each cluster carries a UIScrollEdgeElementContainerInteraction bound to the scroll view's edge, which renders the real progressive blur+fade beneath the buttons as pills pass under. Pre-26 clusters keep an opaque background. Content insets track the cluster widths (the attachment button is capability-gated), resting the pills between the clusters. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The UIKit-hosted bar livelocked SwiftUI (hosting-controller sizing feedback re-rendered every frame) and blanked the composer, which also made the pills unscrollable. The pills return to the proven SwiftUI ScrollView under safe-area-inset button clusters; a zero-size probe in the scroll content walks to the backing UIScrollView and a coordinator binds UIScrollEdgeElementContainerInteraction to transparent container views behind each cluster. Fail-soft: if the probe finds no scroll view or the OS predates iOS 26, nothing attaches and the bar just underlaps. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two native attempts failed structurally: SwiftUI's scrollEdgeEffectStyle never renders for floating siblings, and hosting the bar (or just the clusters) in UIKit for UIScrollEdgeElementContainerInteraction either livelocked the view graph or dropped cluster content, because the effect's shape must come from the container's descendants. The bar now stays pure SwiftUI: clusters carry an ultraThinMaterial background (full blur under the buttons) and a 24pt gradient-masked material band beside each cluster fades passing pills into the bar background -- the same progressive blur+fade the system effect draws, with no UIKit bridging left to break scrolling or layout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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. |
There was a problem hiding this comment.
34 issues found across 88 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMinimalLayout.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMinimalLayout.swift:178">
P2: Shell or unrecognized templates can still show a model pill after the template list is refreshed, exposing a model menu whose selections are rejected by `selectModel`. Gate this condition on the selected template having a `MobileTaskAgentProvider` (or clear the stale selection before passing `models`) so the UI preserves the known-provider-only invariant.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuContent.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuContent.swift:54">
P2: Some model choices shown for a non-selected agent cannot actually be selected after that provider has a cached discovery result, because this submenu uses the curated catalog while `selectTemplate` validates against the provider's discovered models. Build each provider submenu from the same per-template availability data used for validation, or make validation accept exactly the catalog displayed here.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DraftState.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DraftState.swift:169">
P1: Relaunching after an attachment-bearing attempt can reuse an idempotency key for attachment-free request bytes; drafts need to invalidate the operation/recovery IDs when staged attachments cannot be restored, or persist enough attachment state to reconstruct the request.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swift:44">
P1: Editing the selected template can leave a model selected for a command/provider that does not support it, causing the picker to remain visible for shell/unrecognized commands and potentially injecting the old provider’s model ID into the new provider’s command. Revalidate or clear the selection whenever the refreshed template’s detected provider changes, while preserving raw-name selections only for the same provider’s delisted models.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerFailureRecoveryContent.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerFailureRecoveryContent.swift:25">
P2: During recovery refresh, VoiceOver loses the refresh action's localized name and exposes only a generic progress/disabled control; retaining an accessibility label for the current refresh action while showing the spinner would keep the operation understandable.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStager.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStager.swift:18">
P3: Images over the 60 MB raw-input ceiling are shown as if compression failed below 8 MB, even when compression was never attempted; use a distinct raw-size error and localized message.</violation>
<violation number="2" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStager.swift:21">
P2: Cancelling an in-flight photo batch can show a false “That image couldn’t be compressed below 8 MB.” alert because `prepare(url:)` returns `nil` on cancellation and this guard converts it to `.imageRejected`; preserve cancellation before mapping `nil` to an image rejection.</violation>
<violation number="3" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStager.swift:61">
P2: A file that changes or materializes with a different size between the metadata read and `copyItem` can appear staged but fail upload because its recorded `byteCount` differs from the copied bytes; validate the copied destination size and use that value before creating the attachment.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskAttachments.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskAttachments.swift:117">
P1: A timeout or connection loss after the Mac accepts any non-final chunk makes the attachment impossible to retry: the next attempt resends offset 0, but the host requires the current staged byte count as the contiguous offset. Making upload retries resume from the host’s acknowledged byte count (or adding a resumable upload/progress query) would preserve the advertised idempotent retry behavior.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskModelAvailability.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskModelAvailability.swift:54">
P1: A template/provider change can submit a model ID belonging to the old provider because this fallback preserves any missing ID without checking provider identity. Scope stale-ID preservation to the same provider/template or clear the selection when the provider changes, while retaining the intended fallback only for a refresh that delists a model from the same provider.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileImageAttachmentPreparer.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileImageAttachmentPreparer.swift:33">
P2: Cancelling an in-flight prepare does not stop stale image-encoding work, so quickly replacing a large image can leave multiple expensive ImageIO jobs running in the background. Make the encode loop cancellation-aware by checking cancellation between compression attempts (or otherwise move the cancellable boundary inside the work).</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPromptCard.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPromptCard.swift:91">
P2: Long discovered model names can make the trailing-chip layout push the paperclip attachment control off the prompt card because the chip refuses horizontal compression. Constraining the chip width and truncating its label, or making this row horizontally scrollable, keeps both controls reachable.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStrip.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStrip.swift:52">
P2: The attachment remove control has only a 22×22 hit region, making the close affordance difficult to activate and inconsistent with the composer’s 44pt activation-target contract. Keep the 18pt icon but give the button a 44×44 frame.</violation>
<violation number="2" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStrip.swift:63">
P2: When request editing is disabled, VoiceOver still exposes the chip’s Remove Attachment action even though the visible close button is disabled and `removeAttachment` rejects the same phase, leaving an accessible action that does nothing. Propagating the disabled state to the combined chip (or omitting the action while disabled) keeps the accessibility behavior consistent.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift:154">
P2: A previously selected model is dropped on relaunch when a cached refresh delists it. The draft restore path validates against the new list but does not pass the draft ID as `previouslyValidModelID`, even though that API is specifically designed to preserve such selections; passing the draft ID here would keep the choice visible and idempotent.</violation>
<violation number="2" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift:860">
P1: Refreshing templates can submit the previous template's model to a different provider because the raw-ID fallback preserves it across template changes; preserve the ID only when the selected template/provider is unchanged, otherwise clear or revalidate it.</violation>
</file>
<file name="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelDiscovery.swift">
<violation number="1" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelDiscovery.swift:74">
P2: A wall-clock rollback can make an expired model list pass this cache check and be served stale beyond the ten-minute TTL; reject negative ages or use a monotonic clock for expiry.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift:118">
P2: Quoted or escaped option words are not recognized as model flags or end-of-options markers, so an explicit selection can leave the template's original model setting in place or rewrite positional arguments; normalizing shell quoting/escaping for comparisons while retaining raw ranges for edits would make quoted and unquoted forms consistent.</violation>
<violation number="2" location="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift:126">
P1: A dangling model flag consumes a subsequent option as its value, so model selection can silently remove options from user-authored templates; treating option-like values as missing and inserting the selected value before them would preserve the option.</violation>
</file>
<file name="Sources/Mobile/MobileHostService+TicketAuthorization.swift">
<violation number="1" location="Sources/Mobile/MobileHostService+TicketAuthorization.swift:69">
P2: A workspace- or terminal-scoped attach ticket can now perform the Mac-wide attachment upload because this branch skips the existing Mac-scoped authorization helper. Applying `ticketMacScopedWorkspaceMutationAuthorizationError(authorization: authorization)` here would keep uploads consistent with the `workspace.create` operation they precede.</violation>
</file>
<file name="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStore.swift">
<violation number="1" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStore.swift:172">
P1: Concurrent retries can corrupt an attachment: both requests may pass the offset check against the same size and append, while each reports a smaller `receivedBytes` than the file actually contains. Serializing by operation/upload ID (or using an equivalent filesystem lock and atomic state transition) would preserve the contiguous-offset and idempotency contracts.</violation>
<violation number="2" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStore.swift:173">
P2: Retrying a successfully staged non-final chunk after a lost RPC response is rejected as out of order. Because progress is appended before the response and no chunk receipt is recorded, the same `uploadID`/offset cannot be retried; recording and validating chunk progress (or otherwise making duplicate chunks return the existing result) would preserve the store's stated retry/idempotency behavior.</violation>
<violation number="3" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStore.swift:201">
P2: Uploaded attachments are not guaranteed to be owner-only: `createFile`/`moveItem` never apply private permissions, and a moved file retains its staging mode. Applying 0700 to the cache/operation directories and 0600 to staged, final, and metadata files—including after atomic writes and moves—would avoid local data exposure.</violation>
<violation number="4" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStore.swift:201">
P1: A filesystem error between moving the final file and writing its completion record makes an attachment unrecoverable or creates a duplicate on retry. Finalization would be safer if the completion state and file move were made recoverable/transactional, or if retry reconciliation detected the already-moved file before starting a new staging upload.</violation>
</file>
<file name="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStoreError.swift">
<violation number="1" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStoreError.swift:2">
P3: This public error carries a human-readable `message` but doesn't conform to `LocalizedError`, so callers using `error.localizedDescription` (e.g. a generic UI alert) get the generic "The operation couldn't be completed" text instead of the message the struct was built to convey. Consider conforming to `LocalizedError` and returning `message` from `errorDescription`, matching the pattern used by other error types in this codebase.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift:42">
P3: This chip's Menu label omits the .id(selectedModelName) identity-swap that the sibling pill treatments add for the same reason. The neighboring modelPill/agentPill in TaskComposerMinimalLayout.swift use both .fixedSize and .id(...), noting that fixedSize alone doesn't stop the UIKit Menu button from animating its frame and clipping the label on iOS 26 — consider matching that pattern here so the trailingChip label doesn't clip when the selected model name changes length.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift:212">
P2: These two "rejects unknown values" tests rely on the host process environment not setting CMUX_UITEST_TASK_COMPOSER_PREVIEW (or the layout/model overrides). If that env var is set on a dev or CI machine running XCUITests/screenshot recipes, the stored-unknown fallback resolves to .classic/.off instead of the asserted .composer/.combined and the test flakes. Pass an explicit environment (e.g. [String:String]()) like the sibling preview tests do to keep these hermetic.</violation>
</file>
<file name="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelProvider.swift">
<violation number="1" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelProvider.swift:11">
P2: The curated model catalog now exists in two independently maintained lists, so Mac fallback discovery and the iOS fallback can drift and present different choices. A shared catalog or an explicit cross-platform consistency test would keep the provider IDs and display names synchronized.</violation>
</file>
<file name="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelParser.swift">
<violation number="1" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelParser.swift:42">
P2: Codex model discovery silently ignores a valid TOML configuration such as `model = 'gpt-5'` because the parser accepts only double-quoted values. Supporting both TOML string forms (or using a TOML parser) would ensure manually edited configs are reflected in the model picker.</violation>
</file>
<file name="Sources/TerminalController+MobileTaskAttachments.swift">
<violation number="1" location="Sources/TerminalController+MobileTaskAttachments.swift:22">
P1: A valid mobile client can consume unbounded disk space by starting uploads with fresh `operation_id` UUIDs. The per-operation 64 MiB cap does not provide a host-wide limit, and this handler creates a new store for every request; abandoned operation directories are only pruned opportunistically after they are already retained for seven days. Adding a process-wide/account-scoped staging quota and rejecting new operations once it is exceeded would prevent the attachment endpoint from becoming a disk-exhaustion vector.</violation>
<violation number="2" location="Sources/TerminalController+MobileTaskAttachments.swift:30">
P2: Attachment uploads synchronously perform directory creation, pruning, metadata reads/writes, and up to a 3 MiB `FileHandle` write in `store.upload`. This RPC is not classified in `ControlCommandExecutionPolicy.socketWorkerMethods`, so `mobile.task.attachment.upload` reaches `v2MobileTaskAttachmentUpload` through the main-actor dispatch and can freeze the app while each chunk is staged. Routing the filesystem operation through the worker lane (with an explicit concurrency/admission limit) would keep large uploads off the UI actor.</violation>
</file>
<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift:46">
P2: A second picker batch can be submitted while the first batch is still finishing. Both `stageSelectedPhotos` and `stageSelectedFiles` cancel the previous task, but each task unconditionally clears the shared `attachmentStagingTask` in its `defer`; the cancelled task can therefore clear the reference to the newer task after the newer task has started. Since the submit controls use `attachmentStagingTask == nil`, this can enable submission before the newest files have been staged, causing those selected files to be absent from the snapshot/upload. Clearing the reference only when it still belongs to the completing batch (for example with a per-batch token or task identity) would preserve the staging gate.</violation>
<violation number="2" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift:137">
P2: A file or image that finishes staging after the user switches the template to a plain shell is still appended here. `selectTemplate` clears the existing attachments when selecting a shell, but the in-flight staging task is not cancelled; the late attachment can therefore reappear in the shell composer. `uploadAttachments` then returns success with no paths when `snapshot.composition.initialCommand == nil`, so the attachment is silently ignored on submit. Rejecting and deleting late results for plain-shell templates (and/or cancelling the staging task during the template change) keeps the UI and submission consistent.</violation>
</file>
<file name="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelShellRunner.swift">
<violation number="1" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelShellRunner.swift:48">
P1: Model discovery can outlive its advertised 5-second deadline and leave the worker request hung. `readDataToEndOfFile()` blocks before `waitUntilExit()`, while the timeout only kills the shell PID; a descendant that inherited stdout can keep the pipe open indefinitely. A nonblocking reader/cancellation pipe and bounded process-group termination would make the deadline effective.
(Based on your team's feedback about nonblocking process pipe draining.) [FEEDBACK_USED].</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| directory: directory, | ||
| workspaceName: workspaceName, | ||
| didEditDirectory: didEditDirectory, | ||
| attachments: attachments.map(\.submissionAttachment), |
There was a problem hiding this comment.
P1: Relaunching after an attachment-bearing attempt can reuse an idempotency key for attachment-free request bytes; drafts need to invalidate the operation/recovery IDs when staged attachments cannot be restored, or persist enough attachment state to reconstruct the request.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DraftState.swift, line 169:
<comment>Relaunching after an attachment-bearing attempt can reuse an idempotency key for attachment-free request bytes; drafts need to invalidate the operation/recovery IDs when staged attachments cannot be restored, or persist enough attachment state to reconstruct the request.</comment>
<file context>
@@ -128,11 +160,13 @@ extension TaskComposerSheet {
directory: directory,
workspaceName: workspaceName,
didEditDirectory: didEditDirectory,
+ attachments: attachments.map(\.submissionAttachment),
operationID: operationID
)
</file context>
| // re-enabling a variant restores the visible choice. | ||
| guard displaySettings.taskComposerModelPickerVariant.renderedVariant != .off, | ||
| let selectedModelID else { return nil } | ||
| return modelAvailability.selectedModel(id: selectedModelID) |
There was a problem hiding this comment.
P1: Editing the selected template can leave a model selected for a command/provider that does not support it, causing the picker to remain visible for shell/unrecognized commands and potentially injecting the old provider’s model ID into the new provider’s command. Revalidate or clear the selection whenever the refreshed template’s detected provider changes, while preserving raw-name selections only for the same provider’s delisted models.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swift, line 44:
<comment>Editing the selected template can leave a model selected for a command/provider that does not support it, causing the picker to remain visible for shell/unrecognized commands and potentially injecting the old provider’s model ID into the new provider’s command. Revalidate or clear the selection whenever the refreshed template’s detected provider changes, while preserving raw-name selections only for the same provider’s delisted models.</comment>
<file context>
@@ -0,0 +1,80 @@
+ // re-enabling a variant restores the visible choice.
+ guard displaySettings.taskComposerModelPickerVariant.renderedVariant != .off,
+ let selectedModelID else { return nil }
+ return modelAvailability.selectedModel(id: selectedModelID)
+ }
+
</file context>
| "upload_id": attachment.id.uuidString, | ||
| "file_name": attachment.displayName, | ||
| "total_bytes": data.count, | ||
| "offset": range.lowerBound, |
There was a problem hiding this comment.
P1: A timeout or connection loss after the Mac accepts any non-final chunk makes the attachment impossible to retry: the next attempt resends offset 0, but the host requires the current staged byte count as the contiguous offset. Making upload retries resume from the host’s acknowledged byte count (or adding a resumable upload/progress query) would preserve the advertised idempotent retry behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskAttachments.swift, line 117:
<comment>A timeout or connection loss after the Mac accepts any non-final chunk makes the attachment impossible to retry: the next attempt resends offset 0, but the host requires the current staged byte count as the contiguous offset. Making upload retries resume from the host’s acknowledged byte count (or adding a resumable upload/progress query) would preserve the advertised idempotent retry behavior.</comment>
<file context>
@@ -0,0 +1,187 @@
+ "upload_id": attachment.id.uuidString,
+ "file_name": attachment.displayName,
+ "total_bytes": data.count,
+ "offset": range.lowerBound,
+ "data_b64": data.subdata(in: range).base64EncodedString(),
+ "last": isLast,
</file context>
| public func selectedModel(id: String?) -> MobileTaskAgentModel? { | ||
| guard let id else { return nil } | ||
| return models.first { $0.id == id } | ||
| ?? MobileTaskAgentModel(id: id, displayName: id) |
There was a problem hiding this comment.
P1: A template/provider change can submit a model ID belonging to the old provider because this fallback preserves any missing ID without checking provider identity. Scope stale-ID preservation to the same provider/template or clear the selection when the provider changes, while retaining the intended fallback only for a refresh that delists a model from the same provider.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskModelAvailability.swift, line 54:
<comment>A template/provider change can submit a model ID belonging to the old provider because this fallback preserves any missing ID without checking provider identity. Scope stale-ID preservation to the same provider/template or clear the selection when the provider changes, while retaining the intended fallback only for a refresh that delists a model from the same provider.</comment>
<file context>
@@ -0,0 +1,56 @@
+ public func selectedModel(id: String?) -> MobileTaskAgentModel? {
+ guard let id else { return nil }
+ return models.first { $0.id == id }
+ ?? MobileTaskAgentModel(id: id, displayName: id)
+ }
+}
</file context>
| if let selectedTemplateID, !templates.contains(where: { $0.id == selectedTemplateID }) { | ||
| self.selectedTemplateID = templates.first?.id | ||
| } | ||
| selectedModelID = selectedModel?.id |
There was a problem hiding this comment.
P1: Refreshing templates can submit the previous template's model to a different provider because the raw-ID fallback preserves it across template changes; preserve the ID only when the selected template/provider is unchanged, otherwise clear or revalidate it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift, line 860:
<comment>Refreshing templates can submit the previous template's model to a different provider because the raw-ID fallback preserves it across template changes; preserve the ID only when the selected template/provider is unchanged, otherwise clear or revalidate it.</comment>
<file context>
@@ -641,6 +857,7 @@ struct TaskComposerSheet: View {
if let selectedTemplateID, !templates.contains(where: { $0.id == selectedTemplateID }) {
self.selectedTemplateID = templates.first?.id
}
+ selectedModelID = selectedModel?.id
// Sync template edits unless the user typed the directory.
syncSuggestedDirectory()
</file context>
| return | ||
| } | ||
| updateSubmissionRequest(reconcileRecovery: true) { | ||
| attachments.append(attachment) |
There was a problem hiding this comment.
P2: A file or image that finishes staging after the user switches the template to a plain shell is still appended here. selectTemplate clears the existing attachments when selecting a shell, but the in-flight staging task is not cancelled; the late attachment can therefore reappear in the shell composer. uploadAttachments then returns success with no paths when snapshot.composition.initialCommand == nil, so the attachment is silently ignored on submit. Rejecting and deleting late results for plain-shell templates (and/or cancelling the staging task during the template change) keeps the UI and submission consistent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift, line 137:
<comment>A file or image that finishes staging after the user switches the template to a plain shell is still appended here. `selectTemplate` clears the existing attachments when selecting a shell, but the in-flight staging task is not cancelled; the late attachment can therefore reappear in the shell composer. `uploadAttachments` then returns success with no paths when `snapshot.composition.initialCommand == nil`, so the attachment is silently ignored on submit. Rejecting and deleting late results for plain-shell templates (and/or cancelling the staging task during the template change) keeps the UI and submission consistent.</comment>
<file context>
@@ -0,0 +1,238 @@
+ return
+ }
+ updateSubmissionRequest(reconcileRecovery: true) {
+ attachments.append(attachment)
+ }
+ }
</file context>
| attachmentStagingTask = Task { @MainActor in | ||
| defer { | ||
| attachmentPhotoSelection = [] | ||
| attachmentStagingTask = nil |
There was a problem hiding this comment.
P2: A second picker batch can be submitted while the first batch is still finishing. Both stageSelectedPhotos and stageSelectedFiles cancel the previous task, but each task unconditionally clears the shared attachmentStagingTask in its defer; the cancelled task can therefore clear the reference to the newer task after the newer task has started. Since the submit controls use attachmentStagingTask == nil, this can enable submission before the newest files have been staged, causing those selected files to be absent from the snapshot/upload. Clearing the reference only when it still belongs to the completing batch (for example with a per-batch token or task identity) would preserve the staging gate.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift, line 46:
<comment>A second picker batch can be submitted while the first batch is still finishing. Both `stageSelectedPhotos` and `stageSelectedFiles` cancel the previous task, but each task unconditionally clears the shared `attachmentStagingTask` in its `defer`; the cancelled task can therefore clear the reference to the newer task after the newer task has started. Since the submit controls use `attachmentStagingTask == nil`, this can enable submission before the newest files have been staged, causing those selected files to be absent from the snapshot/upload. Clearing the reference only when it still belongs to the completing batch (for example with a per-batch token or task identity) would preserve the staging gate.</comment>
<file context>
@@ -0,0 +1,238 @@
+ attachmentStagingTask = Task { @MainActor in
+ defer {
+ attachmentPhotoSelection = []
+ attachmentStagingTask = nil
+ }
+ for item in items.prefix(remainingAttachmentCount) {
</file context>
| originalFileName: String | ||
| ) async throws -> TaskComposerAttachment { | ||
| let sourceByteCount = try sourceFileByteCount(at: sourceURL) | ||
| guard sourceByteCount <= MobileImageAttachmentPreparer.maximumRawInputBytes else { |
There was a problem hiding this comment.
P3: Images over the 60 MB raw-input ceiling are shown as if compression failed below 8 MB, even when compression was never attempted; use a distinct raw-size error and localized message.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStager.swift, line 18:
<comment>Images over the 60 MB raw-input ceiling are shown as if compression failed below 8 MB, even when compression was never attempted; use a distinct raw-size error and localized message.</comment>
<file context>
@@ -0,0 +1,124 @@
+ originalFileName: String
+ ) async throws -> TaskComposerAttachment {
+ let sourceByteCount = try sourceFileByteCount(at: sourceURL)
+ guard sourceByteCount <= MobileImageAttachmentPreparer.maximumRawInputBytes else {
+ throw StagingError.imageRejected
+ }
</file context>
| @@ -0,0 +1,17 @@ | |||
| /// A wire-ready mobile task attachment storage failure. | |||
| public struct MobileTaskAttachmentStoreError: Error, Equatable, Sendable { | |||
There was a problem hiding this comment.
P3: This public error carries a human-readable message but doesn't conform to LocalizedError, so callers using error.localizedDescription (e.g. a generic UI alert) get the generic "The operation couldn't be completed" text instead of the message the struct was built to convey. Consider conforming to LocalizedError and returning message from errorDescription, matching the pattern used by other error types in this codebase.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStoreError.swift, line 2:
<comment>This public error carries a human-readable `message` but doesn't conform to `LocalizedError`, so callers using `error.localizedDescription` (e.g. a generic UI alert) get the generic "The operation couldn't be completed" text instead of the message the struct was built to convey. Consider conforming to `LocalizedError` and returning `message` from `errorDescription`, matching the pattern used by other error types in this codebase.</comment>
<file context>
@@ -0,0 +1,17 @@
+/// A wire-ready mobile task attachment storage failure.
+public struct MobileTaskAttachmentStoreError: Error, Equatable, Sendable {
+ /// RPC error code.
+ public let code: String
</file context>
| } | ||
| .disabled(isDisabled) | ||
| .taskComposerModelAccessibility(valueName: selectedModelName) | ||
| .accessibilityIdentifier("MobileTaskComposerModelChip") |
There was a problem hiding this comment.
P3: This chip's Menu label omits the .id(selectedModelName) identity-swap that the sibling pill treatments add for the same reason. The neighboring modelPill/agentPill in TaskComposerMinimalLayout.swift use both .fixedSize and .id(...), noting that fixedSize alone doesn't stop the UIKit Menu button from animating its frame and clipping the label on iOS 26 — consider matching that pattern here so the trailingChip label doesn't clip when the selected model name changes length.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift, line 42:
<comment>This chip's Menu label omits the .id(selectedModelName) identity-swap that the sibling pill treatments add for the same reason. The neighboring modelPill/agentPill in TaskComposerMinimalLayout.swift use both .fixedSize and .id(...), noting that fixedSize alone doesn't stop the UIKit Menu button from animating its frame and clipping the label on iOS 26 — consider matching that pattern here so the trailingChip label doesn't clip when the selected model name changes length.</comment>
<file context>
@@ -0,0 +1,49 @@
+ }
+ .disabled(isDisabled)
+ .taskComposerModelAccessibility(valueName: selectedModelName)
+ .accessibilityIdentifier("MobileTaskComposerModelChip")
+ }
+
</file context>
There was a problem hiding this comment.
2 issues found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPillBarViewController.swift">
<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPillBarViewController.swift:21">
P3: The native UIKit pillar bar duplicates layout constants (`fixedClusterHorizontalPadding = 16`, `fixedControlVisualInset = 3`, `clusterGap = 10`) that must stay in lockstep with paddings defined in the SwiftUI layer (`TaskComposerMinimalLayout`'s `leadingCluster`/`trailingCluster` `.padding(.leading/.trailing, 16)`). Because the two values live in different files with no shared constant, a future change to the SwiftUI cluster padding will silently misalign the native capsule glass geometry and the pills' scroll-to-clear spacing without any compile-time signal. Consider deriving these values from a single shared metric (e.g., passing the paddings in from the SwiftUI side through `TaskComposerPillBar`, or exposing a shared constant) so the native overlay cannot drift.</violation>
<violation number="2" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPillBarViewController.swift:56">
P2: VoiceOver can announce the model/agent pills before the leading attachment/options controls because the UIKit sibling order is opposite the composer's visual order; an explicit accessibility element order (leading host, pills host, trailing host) would keep keyboard and assistive-technology navigation consistent with the UI.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
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. |
…cker # Conflicts: # Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandExecutionPolicyTests.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. |
Adds model selection to the iOS New Task sheet with five UX variants, switchable at runtime from Settings → CMUX Labs → New Task Model Lab (DEBUG builds only; release builds keep the feature off).
Mechanism. A curated per-provider model catalog (
MobileTaskAgentModelCatalog) detects the provider from the template command's first token (claude,codex,opencode) and, only when the user explicitly picks a model, inserts that provider's flag right after the executable token (claude --model 'claude-opus-4-8' …,codex -m 'gpt-5.5' …,opencode --model '…' …). With no model selected the template command stays byte-for-byte verbatim, preserving the composer's no-shell-parsing invariant and all existing verbatim tests. The selection rides the existing draft/snapshot pipeline (MobileTaskComposerDraft.modelID,MobileTaskSubmissionSnapshot.modelID), so it persists across relaunches and stays idempotency-safe; legacy drafts without the field decode fine.Variants (lab enum
TaskComposerModelPickerVariant, default in DEBUG = A):combined— the agent menu becomes agent+model: agents with models get a submenu (Default + models); label reads "Ask Claude · Opus 4.8".separateRow— dedicated Model menu row under the agent selector.trailingChip— compact capsule chip on the agent row opening a model menu.pillStrip— one-tap model pills under the agent row.contextRow— Model row in the context card beside Mac/Directory.Model UI only appears for templates whose command starts with a known provider binary (never for Shell or unrecognized custom commands). All new strings localized (en + ja). Tests: catalog detection/injection/quoting, composer opt-in invariant, draft/snapshot round-trips (187 pass in CmuxMobileShellModel).
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Adds model selection to the iOS New Task composer with a lab-switchable layout, discovers models from connected Macs, and adds chunked task attachments. Commands are only rewritten when a model is explicitly selected; otherwise the template stays byte-for-byte unchanged.
New Features
task.models.v1: iOS callsmobile.task.models.listper provider, caches per Mac/provider, and falls back to curated models when discovery isn’t available; UI only for known providers.mobile.task.attachment.upload; caps—10 items, 8 MiB/image, 32 MiB/file, 64 MiB total; shown only when the selected Mac advertisestask.attachments.v1; attachment paths flow viaCMUX_TASK_ATTACHMENTSand are appended to the prompt.Bug Fixes
--model/-monly before--, never in quotes; stop at the first simple-command boundary (incl. control operators, relevant redirections, and word-initial#); handle attached separators and update all hits; if commented, insert after the first token instead.MacPairingKeylookups to resolve the exact tagged instance (or any same-device pairing), fixing detection fortask.attachments.v1andtask.models.v1.Written for commit e08cb75. Summary will update on new commits.
Summary by CodeRabbit