Merge nucleic/warm-quartz-marten-mswp into dev
This commit is contained in:
@@ -6,18 +6,16 @@ import NucleicProtocol
|
||||
/// The division of labor with the Mac is deliberate and worth stating once here, because it's
|
||||
/// what makes a rail on a thin remote client honest:
|
||||
///
|
||||
/// - **The host routes.** The matrix, the connected providers, the Settings pin, live quota and
|
||||
/// provider incidents live on the Mac. A send carries the rail's *stop*, never a concrete pair
|
||||
/// (`StartChatRequest.intelligence`, `ClientMsg.setSessionIntelligence`), and the host resolves
|
||||
/// it with its own classifier. So the phone can never pin a worse model than the Mac would.
|
||||
/// - **The host publishes the routes.** The matrix, connected providers, Settings pin, live quota,
|
||||
/// and provider incidents live on the Mac. The phone only chooses among the concrete routes in
|
||||
/// that projection; it does not recreate the matrix.
|
||||
/// - **The phone previews.** It runs the shared `HeuristicPurposeClassifier` — the same code the
|
||||
/// host runs, moved into NucleicProtocol precisely so both sides classify identically — over
|
||||
/// the draft, and looks the answer up in the host's projected route table. Sub-millisecond, no
|
||||
/// round trip, so the route line can follow every keystroke on a cellular link.
|
||||
/// - **The preview can be wrong, and says so by being a preview.** The host's send-time
|
||||
/// classification runs a fuller stack; when the two disagree, the transcript's routing note
|
||||
/// records what actually ran. That's the same relationship the Mac's own pre-send preview has
|
||||
/// with its send-time resolve.
|
||||
/// - **The displayed pair is the launch authority.** A new-chat send carries the stop *and* the
|
||||
/// projected model/effort pair. The host preserves that explicit pair rather than classifying a
|
||||
/// second time, so the session cannot start on a model the route line never showed.
|
||||
extension RemoteStore {
|
||||
|
||||
/// Whether the context host offers the rail at all. False keeps the composers on their manual
|
||||
|
||||
@@ -2741,10 +2741,10 @@ final class RemoteStore: ObservableObject {
|
||||
|
||||
// MARK: - Control intents (control scope; the same actions the Mac can take)
|
||||
|
||||
/// Start a chat on the owning Mac. `intelligence` is the rail's stop: pass it and leave
|
||||
/// `model` nil to have the host classify the opening message and route it (the composer's
|
||||
/// normal path); pass a concrete `model`/`effort` instead when the user pinned one. Sending
|
||||
/// both leaves the explicit pair in charge — see `AppStore.remoteStartSelection`.
|
||||
/// Start a chat on the owning Mac. The routed composer passes `intelligence` together with
|
||||
/// the concrete model/effort displayed from the host's projected route table: the stop keeps
|
||||
/// the rail positioned, while the explicit pair guarantees the session runs what was shown.
|
||||
/// Older clients may still pass only the stop and let the host resolve it.
|
||||
func startChat(in projectID: ProjectID, message: String, model: String? = nil,
|
||||
effort: String? = nil, baseBranch: String? = nil,
|
||||
useWorktree: Bool = true, auto: Bool? = nil,
|
||||
|
||||
@@ -287,7 +287,7 @@ struct StartChatComposer: View {
|
||||
orchestraAvailable: store.intelligenceCatalog.orchestraAvailable,
|
||||
composerText: draft,
|
||||
routeDescription: store.intelligenceRouteDescription(
|
||||
for: draft, level: previewLevel),
|
||||
for: routingPrompt, level: previewLevel),
|
||||
unavailableReason: store.canControl ? nil : "This device is view-only.",
|
||||
onPreviewChanged: { intelligencePreview = $0 })
|
||||
IntelligenceRouteLine(
|
||||
@@ -317,27 +317,47 @@ struct StartChatComposer: View {
|
||||
private var previewLevel: Int { intelligencePreview ?? intelligence }
|
||||
|
||||
private var previewRoute: WireIntelligenceCatalog.Route? {
|
||||
store.intelligenceRoute(for: draft, level: previewLevel)
|
||||
store.intelligenceRoute(for: routingPrompt, level: previewLevel)
|
||||
}
|
||||
|
||||
/// The route at the committed stop, distinct from the under-finger preview. This exact pair is
|
||||
/// copied into the start request so the host cannot classify again and run something other
|
||||
/// than the model and effort named by the route line.
|
||||
private var committedRoute: WireIntelligenceCatalog.Route? {
|
||||
store.intelligenceRoute(for: routingPrompt, level: intelligence)
|
||||
}
|
||||
|
||||
/// Attachment-only starts still need a visible concrete route. The host ultimately adds file
|
||||
/// references to the opening prompt; filenames give the shared classifier equivalent context
|
||||
/// before launch and, critically, are used by both the route line and the committed request.
|
||||
private var routingPrompt: String {
|
||||
let text = draft.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard text.isEmpty, !attachments.isEmpty else { return draft }
|
||||
return "Work with the attached files: " + attachments.map(\.filename).joined(separator: ", ")
|
||||
}
|
||||
|
||||
private var orchestraActive: Bool { orchestra || MobileEfforts.isOrchestra(effort) }
|
||||
|
||||
private var canStart: Bool {
|
||||
selected != nil && store.canControl
|
||||
&& !(draft.trimmingCharacters(in: .whitespaces).isEmpty && attachments.isEmpty)
|
||||
guard selected != nil, store.canControl,
|
||||
!(draft.trimmingCharacters(in: .whitespaces).isEmpty && attachments.isEmpty)
|
||||
else { return false }
|
||||
guard store.routesIntelligence, !orchestra else { return true }
|
||||
return committedRoute?.isAvailable == true
|
||||
}
|
||||
|
||||
/// Start the chat. When the rail is driving, the request carries the **stop** and no model:
|
||||
/// the host classifies the real prompt with its own stack and picks the pair, so the phone
|
||||
/// never pins a worse model than the Mac would have (see `RemoteIntelligence`). A host that
|
||||
/// doesn't route sends the concrete values from its manual menus instead.
|
||||
/// Start the chat. When the rail is driving, the request carries both its stop and the exact
|
||||
/// concrete pair shown on screen. The stop preserves the rail position; the pair is the launch
|
||||
/// authority, preventing a second host-side classification from contradicting the route line.
|
||||
private func start() {
|
||||
guard let project = selected else { return }
|
||||
let routed = store.routesIntelligence && !orchestra
|
||||
let sentModel = routed ? nil : model
|
||||
let route = routed ? committedRoute : nil
|
||||
guard !routed || route?.isAvailable == true else { return }
|
||||
let sentModel = routed ? route?.model : model
|
||||
let sentEffort = orchestra
|
||||
? store.modelCatalog.orchestraSentinelOrFallback
|
||||
: (routed ? nil : effort)
|
||||
: (routed ? route?.effort : effort)
|
||||
let sentLevel = store.routesIntelligence ? intelligence : nil
|
||||
|
||||
if runOnMesh && meshAvailable {
|
||||
|
||||
Reference in New Issue
Block a user