Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Native/OptChat/chief-kernel/src/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,11 @@ cmux Home, and your final reply of each turn is posted there.
bundle id and path, pid), whether the CLI is not the app's own, the update
state (phase, ready on quit, last check), the bundled acpmux and Chief
host stamps and macOS. Run it before you say which build the user runs.
- The user's sign-ins from another browser: only when the user asks, run
`cmux browser import-cookies --from chrome[:PROFILE] --domain SITE
--reason \"why\"` (the fewest sites) from your shell with a 4-minute
timeout: it waits up to 3 minutes for the user's Allow in cmux. On a
denial nothing is imported: say so.
- Your engine: `chief engine show` prints your harness, model and effort
and the last turn's stats; `chief engine set --harness H --model M
--effort E` changes them from the next turn (only when the user asks).
Expand Down Expand Up @@ -816,6 +821,11 @@ cmux Home, and your final reply of each turn is posted there.
bundle id and path, pid), whether the CLI is not the app's own, the update
state (phase, ready on quit, last check), the bundled acpmux and Chief
host stamps and macOS. Run it before you say which build the user runs.
- The user's sign-ins from another browser: only when the user asks, run
`cmux browser import-cookies --from chrome[:PROFILE] --domain SITE
--reason \"why\"` (the fewest sites) from your shell with a 4-minute
timeout: it waits up to 3 minutes for the user's Allow in cmux. On a
denial nothing is imported: say so.
- Your engine: `{chief} engine show` prints your harness, model and effort
and the last turn's stats; `{chief} engine set --harness H --model M
--effort E` changes them from the next turn (only when the user asks).
Expand Down
5 changes: 5 additions & 0 deletions Native/OptChat/chief-kernel/tests/golden/cli-everything.txt
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ cmux Home, and your final reply of each turn is posted there.
bundle id and path, pid), whether the CLI is not the app's own, the update
state (phase, ready on quit, last check), the bundled acpmux and Chief
host stamps and macOS. Run it before you say which build the user runs.
- The user's sign-ins from another browser: only when the user asks, run
`cmux browser import-cookies --from chrome[:PROFILE] --domain SITE
--reason "why"` (the fewest sites) from your shell with a 4-minute
timeout: it waits up to 3 minutes for the user's Allow in cmux. On a
denial nothing is imported: say so.
- Your engine: `/h/optchat/bin/chief engine show` prints your harness, model and effort
and the last turn's stats; `/h/optchat/bin/chief engine set --harness H --model M
--effort E` changes them from the next turn (only when the user asks).
Expand Down
5 changes: 5 additions & 0 deletions Native/OptChat/chief-kernel/tests/golden/cli-plain.txt
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ cmux Home, and your final reply of each turn is posted there.
bundle id and path, pid), whether the CLI is not the app's own, the update
state (phase, ready on quit, last check), the bundled acpmux and Chief
host stamps and macOS. Run it before you say which build the user runs.
- The user's sign-ins from another browser: only when the user asks, run
`cmux browser import-cookies --from chrome[:PROFILE] --domain SITE
--reason "why"` (the fewest sites) from your shell with a 4-minute
timeout: it waits up to 3 minutes for the user's Allow in cmux. On a
denial nothing is imported: say so.
- Your engine: `/h/optchat/bin/chief engine show` prints your harness, model and effort
and the last turn's stats; `/h/optchat/bin/chief engine set --harness H --model M
--effort E` changes them from the next turn (only when the user asks).
Expand Down
5 changes: 5 additions & 0 deletions Native/OptChat/chief-kernel/tests/golden/mcp-contact.txt
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ cmux Home, and your final reply of each turn is posted there.
bundle id and path, pid), whether the CLI is not the app's own, the update
state (phase, ready on quit, last check), the bundled acpmux and Chief
host stamps and macOS. Run it before you say which build the user runs.
- The user's sign-ins from another browser: only when the user asks, run
`cmux browser import-cookies --from chrome[:PROFILE] --domain SITE
--reason "why"` (the fewest sites) from your shell with a 4-minute
timeout: it waits up to 3 minutes for the user's Allow in cmux. On a
denial nothing is imported: say so.
- Your engine: `chief engine show` prints your harness, model and effort
and the last turn's stats; `chief engine set --harness H --model M
--effort E` changes them from the next turn (only when the user asks).
Expand Down
5 changes: 5 additions & 0 deletions Native/OptChat/chief-kernel/tests/golden/mcp-everything.txt
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ cmux Home, and your final reply of each turn is posted there.
bundle id and path, pid), whether the CLI is not the app's own, the update
state (phase, ready on quit, last check), the bundled acpmux and Chief
host stamps and macOS. Run it before you say which build the user runs.
- The user's sign-ins from another browser: only when the user asks, run
`cmux browser import-cookies --from chrome[:PROFILE] --domain SITE
--reason "why"` (the fewest sites) from your shell with a 4-minute
timeout: it waits up to 3 minutes for the user's Allow in cmux. On a
denial nothing is imported: say so.
- Your engine: `chief engine show` prints your harness, model and effort
and the last turn's stats; `chief engine set --harness H --model M
--effort E` changes them from the next turn (only when the user asks).
Expand Down
5 changes: 5 additions & 0 deletions Native/OptChat/chief-kernel/tests/golden/mcp-no-user.txt
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ cmux Home, and your final reply of each turn is posted there.
bundle id and path, pid), whether the CLI is not the app's own, the update
state (phase, ready on quit, last check), the bundled acpmux and Chief
host stamps and macOS. Run it before you say which build the user runs.
- The user's sign-ins from another browser: only when the user asks, run
`cmux browser import-cookies --from chrome[:PROFILE] --domain SITE
--reason "why"` (the fewest sites) from your shell with a 4-minute
timeout: it waits up to 3 minutes for the user's Allow in cmux. On a
denial nothing is imported: say so.
- Your engine: `chief engine show` prints your harness, model and effort
and the last turn's stats; `chief engine set --harness H --model M
--effort E` changes them from the next turn (only when the user asks).
Expand Down
5 changes: 5 additions & 0 deletions Native/OptChat/chief-kernel/tests/golden/mcp-plain.txt
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ cmux Home, and your final reply of each turn is posted there.
bundle id and path, pid), whether the CLI is not the app's own, the update
state (phase, ready on quit, last check), the bundled acpmux and Chief
host stamps and macOS. Run it before you say which build the user runs.
- The user's sign-ins from another browser: only when the user asks, run
`cmux browser import-cookies --from chrome[:PROFILE] --domain SITE
--reason "why"` (the fewest sites) from your shell with a 4-minute
timeout: it waits up to 3 minutes for the user's Allow in cmux. On a
denial nothing is imported: say so.
- Your engine: `chief engine show` prints your harness, model and effort
and the last turn's stats; `chief engine set --harness H --model M
--effort E` changes them from the next turn (only when the user asks).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ public nonisolated struct ActionInvocation: Sendable, Hashable {
/// `caller.terminal_id`: the terminal whose cmux CLI sent the run (an agent running in a
/// terminal), nil for any other run. Like ``callerAgentSession``, the caller's own claim.
public var callerTerminal: String?
/// `caller.home_chat`: a Chief turn sent the run (the Chief host sets it); its chat is Home's.
/// Like ``callerAgentSession``, the caller's own claim: it names who asks, it grants nothing.
public var callerHomeChat = false
/// A split that opens a terminal even where a person's split opens the New Tab page (the chat
/// header's Terminal, cx-00n1). In-app only: the control socket never sets it.
public var opensTerminal = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,12 @@ enum OnboardingHandlers {
workspace.screens.flatMap(\.panes).flatMap(\.tabs).map { tab in
CookieImportCandidate(tabID: tab.id, title: tab.displayTitle, workspace: workspace.displayName,
agentSession: agentTabs.sessions[agentTabs.resolve(tab.id)] ?? tab.agentSession?.session,
terminalIDs: [tab.terminalID?.rawValue, tab.terminalResourceID?.rawValue].compactMap(\.self))
terminalIDs: [tab.terminalID?.rawValue, tab.terminalResourceID?.rawValue].compactMap(\.self),
isHomeChat: tab.isHomeDockPage && workspace.kind == "home")
}
}
// The caller's acpmux session, by id (O(1)): its title and latest reply.
return CookieImportRequester(invocation, among: tabs) { session in
services.agentLanes.lane(session).map { ($0.title, $0.preview) }
}
return CookieImportRequester(invocation, among: tabs) { services.agentLanes.lane($0).map { ($0.title, $0.preview) } }
},
target: { invocation in
let id = BookmarkResolver(services: services).profile(invocation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ public final class RegistryControlBridge: ControlActionExecutor {
invocation.opensInBackground = request.background
invocation.callerAgentSession = request.callerAgentSession
invocation.callerTerminal = request.callerTerminal
invocation.callerHomeChat = request.callerHomeChat
return invocation
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ public struct CookieImportCandidate: Sendable, Equatable {
public var agentSession: String?
/// The terminal and terminal resource ids of a terminal tab, empty for other tabs.
public var terminalIDs: [String]
/// Home's docked chat tab (page `home-dock`), where a Chief's turns post: the tab a Chief's run
/// (`caller.home_chat`) reveals.
public var isHomeChat: Bool

/// Creates a candidate.
public init(tabID: String, title: String, workspace: String, agentSession: String?, terminalIDs: [String]) {
public init(tabID: String, title: String, workspace: String, agentSession: String?, terminalIDs: [String],
isHomeChat: Bool = false) {
self.tabID = tabID
self.title = title
self.workspace = workspace
self.agentSession = agentSession
self.terminalIDs = terminalIDs
self.isHomeChat = isHomeChat
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,31 @@ public struct CookieImportRequester: Sendable, Equatable {
self.latestMessage = latestMessage
}

/// The name a Chief's run shows (`caller.home_chat`): its turn's session is temporary and has
/// no tab, so the panel names the Chief and reveals Home's chat (#32960).
static let chiefName = "Chief"

/// Finds who asked among the open tabs: the agent chat whose session is
/// `caller.agent_session`, else the terminal whose id is `caller.terminal_id`.
/// `caller.agent_session`, else Home's chat for a Chief's turn (`caller.home_chat`), else
/// that session's acpmux chat, else the terminal whose id is `caller.terminal_id`.
///
/// - Parameters:
/// - invocation: The run; its caller fields name the requester.
/// - tabs: Every open tab, in workspace order.
/// - chat: The acpmux chat of a session (title and latest reply), nil when unknown.
public init(_ invocation: ActionInvocation, among tabs: [CookieImportCandidate],
chat: (String) -> (title: String?, preview: String?)?) {
if let session = invocation.callerAgentSession {
let found = tabs.first { $0.agentSession == session }
let latest = chat(session)
self.init(kind: .agentChat, name: found?.title ?? latest?.title ?? session, workspace: found?.workspace,
tabID: found?.tabID, latestMessage: latest?.preview)
let session = invocation.callerAgentSession
let latest = session.flatMap(chat)
if let session, let found = tabs.first(where: { $0.agentSession == session }) {
self.init(kind: .agentChat, name: found.title, workspace: found.workspace, tabID: found.tabID,
latestMessage: latest?.preview)
} else if invocation.callerHomeChat {
let home = tabs.first(where: \.isHomeChat)
self.init(kind: .agentChat, name: Self.chiefName, workspace: home?.workspace, tabID: home?.tabID,
latestMessage: latest?.preview)
} else if let session {
self.init(kind: .agentChat, name: latest?.title ?? session, latestMessage: latest?.preview)
} else if let terminal = invocation.callerTerminal, let found = tabs.first(where: { $0.terminalIDs.contains(terminal) }) {
self.init(kind: .terminal, name: found.title, workspace: found.workspace, tabID: found.tabID)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ public final class CookieImportService {
}

/// An agent must say why. A person typing the command at a terminal (`cli` origin with no
/// agent session) may leave it out; the panel still asks them.
/// agent session and no Chief turn) may leave it out; the panel still asks them.
nonisolated static func needsReason(_ invocation: ActionInvocation) -> Bool {
invocation.origin != .user && !(invocation.origin == .cli && invocation.callerAgentSession == nil)
invocation.origin != .user
&& !(invocation.origin == .cli && invocation.callerAgentSession == nil && !invocation.callerHomeChat)
}

private func importCookies(query: CookieSourceQuery, filter: CookieDomainFilter, reason: String, target: (id: String, name: String),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ public final class TabModel: Identifiable {
kind == .conversation ? conversation?.page : nil
}

/// Home's docked chat page tab (`WorkspaceModel.homeDockPage`), where a Chief's turns post.
public var isHomeDockPage: Bool { page == WorkspaceModel.homeDockPage }

/// What a page tab shows (`page-tab-route-v1`); nil for none or any other tab.
public var pageRoute: String? {
kind == .conversation ? conversation?.route : nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ import Testing
var agentAtCLI = ActionInvocation(origin: .cli)
agentAtCLI.callerAgentSession = "01a15270"
#expect(CookieImportService.needsReason(agentAtCLI))
var chiefAtCLI = ActionInvocation(origin: .cli)
chiefAtCLI.callerHomeChat = true
#expect(CookieImportService.needsReason(chiefAtCLI), "a Chief turn is an agent")
#expect(CookieImportService.needsReason(ActionInvocation(origin: .mcp)))
#expect(CookieImportService.needsReason(ActionInvocation(origin: .script)), "an agent's shell tool has no terminal")
}
Expand All @@ -105,6 +108,19 @@ import Testing
#expect(requester == CookieImportRequester(kind: .agentChat, name: "old chat"))
}

/// A Chief's run (`caller.home_chat` with its turn's temporary session, #32960) names the Chief
/// and reveals Home's chat, never the turn session's raw id.
@Test func requesterOfAChiefTurnIsTheChiefInHome() {
var invocation = ActionInvocation(origin: .mcp)
invocation.callerAgentSession = "optchat-turn-9f2"
invocation.callerHomeChat = true
let tabs = Self.tabs + [CookieImportCandidate(tabID: "tab-home", title: "Home", workspace: "Home", agentSession: nil,
terminalIDs: [], isHomeChat: true)]
let requester = CookieImportRequester(invocation, among: tabs) { _ in (title: "optchat-turn-9f2", preview: "Importing your GitHub sign-in") }
#expect(requester == CookieImportRequester(kind: .agentChat, name: "Chief", workspace: "Home", tabID: "tab-home",
latestMessage: "Importing your GitHub sign-in"))
}

@Test func requesterIsTheTerminalTheCallerNames() {
var invocation = ActionInvocation(origin: .cli)
invocation.callerTerminal = "res-7"
Expand Down
1 change: 1 addition & 0 deletions cmux-next-ios/verify/parity-mac.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ a stated limit), **missing** (not on the phone yet), **not from a phone**
| Chief conversation, questions | Home > Chief | works | Answers need the phone's signature. |
| Stop the Chief, Chief settings | Chief settings | works | `chief.stop`, `chief.engine.*`. |
| New Chief…, Archive Chief, Memory Inspector | — | missing | |
| Chief imports browser cookies on request (`cmux browser import-cookies` from the Chief's shell, https://github.com/manaflow-ai/cmux/issues/32960) | Home > Chief: ask the Chief | partial | The request can come from the phone's Chief chat, but the consent panel opens on the Mac and the Mac's Keychain prompt follows Allow. The other browser's cookies live on that Mac, so only someone at the Mac may allow reading them; no phone-side Allow. |
| Cloud desktop, **Open Desktop** (the Cloud machine's screen) | Chief info > **Computer** (`chief.settings.computer`) | partial | View only, by design: hostd drops touches, text, keys and buttons for the `c~<machine_id>~<team_id>` `surface` key, and the card sends none. Shown only when the Mac's `cloud-directory` says `paid` and a machine `ran`; the first running machine, else the first that ran. Paused or pausing: "Computer is asleep"; starting or provisioning: "Waking up…"; else "Computer isn't available". Needs `cloud-desktop-v1` for the live screen (https://github.com/manaflow-ai/cmux/issues/31488). code-checked; live pending |
| Show Feed, Jump to Latest Unread, Mark read | Feed tab, swipe | works | |
| Clear All Notifications | Feed > Filter menu > **Clear All Notifications** | works (parity2) | `notification.clear` on the Mac's current session (new on the lane, see SECURITY.md); the Feed's terminal events go with it. No confirmation, as on the Mac. |
Expand Down
Loading