Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b9ecb4b
test(cmux-tui): agent-session-adopt binds a chat of this machine, ref…
teamleaderleo Oct 21, 2026
b2c5a86
feat(cmux-tui): agent-session-adopt binds a chat of this machine's ac…
teamleaderleo Oct 21, 2026
cebd26a
feat(next): open another machine's running agent chat as a sidebar wo…
teamleaderleo Oct 21, 2026
09b0238
fix(next): the bypass entry and its string are nonisolated (row conte…
teamleaderleo Oct 21, 2026
4c7d71e
test(cmux-tui): the adopted chat's mode reads per tab, rides the atta…
teamleaderleo Oct 21, 2026
fbab657
feat(cmux-tui): agent-session-mode reads a tab's chat mode; attach ca…
teamleaderleo Oct 21, 2026
b2dbf70
fix(next): the bypass warning follows the remote daemon's mode; --hos…
teamleaderleo Oct 21, 2026
1e8c382
Merge commit 'dad74136bf6dc4272ada52e8798995aedc817dcc' into adopt-merge
teamleaderleo Oct 21, 2026
ad4996b
fix(next): a host that names another machine opens there; a host nami…
teamleaderleo Oct 21, 2026
4d233d1
fix(next): AgentBypassSessions imports Foundation publicly (its note …
teamleaderleo Oct 21, 2026
2c8f18c
fix(next): AgentTabRemoteClient imports Foundation publicly (its API …
teamleaderleo Oct 21, 2026
632e5c9
fix(next): the group join closure is typed (the compiler could not in…
teamleaderleo Oct 21, 2026
217d294
fix(next): the group join is a function (the closure's if/else did no…
teamleaderleo Oct 21, 2026
6f5e79f
fix: agent-session-mode and adopt refuse a session acpmux no longer k…
teamleaderleo Oct 21, 2026
5d37659
fix(next): the group join function has its own name (the local closur…
teamleaderleo Oct 21, 2026
0801706
Merge remote-tracking branch 'origin/feat-cmux-next' into HEAD
teamleaderleo Oct 21, 2026
7693d28
fix(next): the group join closure is built without a nested map (the …
teamleaderleo Oct 21, 2026
e0f0a48
Merge origin/feat-cmux-next e323f77325da into cmux/remote-session-adopt
teamleaderleo Oct 21, 2026
169626f
test(cmux-tui): the attach tests' fake acpmux answers the attach's mo…
teamleaderleo Oct 21, 2026
b5122f7
test(next): another machine's chat is refused without the agent page;…
teamleaderleo Oct 21, 2026
5d87ec5
test(next): the no-page adopt test runs as a build without the agent …
teamleaderleo Oct 21, 2026
211a403
fix(next): adopt refuses without the agent page before any workspace …
teamleaderleo Oct 21, 2026
1b9b300
Merge branch 'feat-cmux-next' into cmux/remote-session-adopt
teamleaderleo Oct 21, 2026
2760c71
Merge commit 'f0662299c05bba60f62606612490f1b39ec01d14' into cmux/rem…
teamleaderleo Oct 22, 2026
9bcf149
test(next): the adopt-without-page test watches a 500 ms window inste…
teamleaderleo Oct 22, 2026
b10a581
Merge origin/feat-cmux-next (6ceb03a41537) into cmux/remote-session-a…
teamleaderleo Oct 23, 2026
0b111f4
test(cmux-next): a refused remote adopt must close the workspace it made
teamleaderleo Oct 23, 2026
3e53b9c
fix(cmux-next): a refused remote adopt closes the workspace it made
teamleaderleo Oct 23, 2026
f070600
fix(cmux-next): any failed remote adopt closes its workspace; the tab…
teamleaderleo Oct 23, 2026
7445cb3
test(cmux-next): a failed adopt's workspace closes after a link drop,…
teamleaderleo Oct 23, 2026
92bc138
fix(cmux-next): a failed adopt closes its workspace on a live link, u…
teamleaderleo Oct 23, 2026
ef650fa
fix(cmux-next, cmux-tui): a failed adopt's close survives later drops…
teamleaderleo Oct 23, 2026
f5d3758
fix(cmux-next): a failed adopt's workspace closes after the repair ta…
teamleaderleo Oct 23, 2026
80236be
fix(cmux-next): a failed adopt's close always ends
teamleaderleo Oct 23, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation

/// Why another machine's running chat could not open here (`agent-session-adopt`, cmux-tui/spec/
/// commands.md), for the refusals only an adopt gives; nil for the rest, which read as a start's
/// (the app's `AgentRemoteStartRefusal`).
public nonisolated struct AgentSessionAdoptRefusal {
public let code: String
public let machine: String

public init(code: String, machine: String) {
self.code = code
self.machine = machine
}

/// The person's text for ``code`` on ``machine``.
public var message: String? {
switch code {
case "agent_session.unknown_session":
String(format: String(localized: "agentPane.adopt.unknownSession",
defaultValue: "%@ runs no such agent chat of its own. Check the session id with acpmux ls on that machine.",
bundle: .module), machine)
case "agent_session.bound":
String(localized: "agentPane.adopt.bound", defaultValue: "That agent tab already shows a chat. Open the session again in a new workspace.",
bundle: .module)
default: nil
}
}

/// ``message`` of `code` on `machine`.
public static func message(code: String, machine: String) -> String? {
Self(code: code, machine: machine).message
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
public import CmuxNextDaemon
public import Foundation

/// ``AgentSessionAttachClient`` as the pane's ``AgentSessionRemoteClient``.
public nonisolated struct AgentTabRemoteClient: AgentSessionRemoteClient {
public let client: AgentSessionAttachClient
public let mode: AgentSessionAttachClient.Mode

public init(client: AgentSessionAttachClient, mode: AgentSessionAttachClient.Mode) {
self.client = client
self.mode = mode
}

public var isReadOnly: Bool { mode == .observe }

public func attach(_ page: AgentSessionPage, events handler: @escaping @Sendable (AgentSessionRemoteEvent) -> Void) async throws -> Data {
try await mapped {
try await client.attach(afterSeq: page.afterSeq, beforeSeq: page.beforeSeq, limit: page.limit, kinds: page.kinds) { event, line in
handler(Self.event(event, line))
}
}
}

public func events(_ page: AgentSessionPage) async throws -> Data {
try await mapped { try await client.events(afterSeq: page.afterSeq, beforeSeq: page.beforeSeq, limit: page.limit, kinds: page.kinds) }
}

public func prompt(id: String, text: String) async throws -> Data {
try await mapped { try await client.prompt(id: id, text: text) }
}

public func cancel() async throws { _ = try await mapped { try await client.cancel(); return Data() } }

public func permission(id: String, option: String, decisionNonce: String?, personDecision: String?) async throws {
_ = try await mapped {
try await client.permission(id: id, option: option, decisionNonce: decisionNonce, personDecision: personDecision)
return Data()
}
}

public func detach() async { await client.detach() }

public func children() async throws -> Data { try await mapped { try await client.children() } }

public func close() async { await client.close() }

/// A daemon push as the pane's event: the record or request object, or the close reason.
public static func event(_ name: String, _ line: Data) -> AgentSessionRemoteEvent {
let object = (try? JSONSerialization.jsonObject(with: line)) as? [String: Any] ?? [:]
let member = { (key: String) -> Data in
guard let value = object[key], JSONSerialization.isValidJSONObject(value),
let data = try? JSONSerialization.data(withJSONObject: value) else { return Data("{}".utf8) }
return data
}
switch name {
case "agent-session-record": return .record(member("record"))
case "agent-session-permission": return .permission(member("request"))
case "agent-session-changed": return .changed(member("change"))
default: return .closed(object["reason"] as? String ?? "closed")
}
}

/// The daemon's refusal as the pane's error (its `agent_session.*` code reaches the page).
public func mapped(_ work: () async throws -> Data) async throws -> Data {
do {
return try await work()
} catch let error as AgentSessionAttachError {
throw AgentSessionRemoteError(code: error.code, message: error.message)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3028,6 +3028,270 @@
}
}
},
"agentPane.adopt.bound": {
"comment": "Shown when another machine's agent chat cannot open because the new agent tab already shows a chat",
"extractionState": "manual",
"localizations": {
"ar": {
"stringUnit": {
"state": "needs_review",
"value": "علامة تبويب الوكيل هذه تعرض محادثة بالفعل. افتح الجلسة مرة أخرى في مساحة عمل جديدة."
}
},
"bs": {
"stringUnit": {
"state": "needs_review",
"value": "Ta kartica agenta već prikazuje razgovor. Ponovo otvorite sesiju u novom radnom prostoru."
}
},
"da": {
"stringUnit": {
"state": "needs_review",
"value": "Den agentfane viser allerede en chat. Åbn sessionen igen i et nyt arbejdsområde."
}
},
"de": {
"stringUnit": {
"state": "needs_review",
"value": "Dieser Agent-Tab zeigt bereits einen Chat. Öffne die Sitzung erneut in einem neuen Arbeitsbereich."
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "That agent tab already shows a chat. Open the session again in a new workspace."
}
},
"es": {
"stringUnit": {
"state": "needs_review",
"value": "Esa pestaña de agente ya muestra un chat. Vuelve a abrir la sesión en un espacio de trabajo nuevo."
}
},
"fr": {
"stringUnit": {
"state": "needs_review",
"value": "Cet onglet d'agent affiche déjà une discussion. Rouvrez la session dans un nouvel espace de travail."
}
},
"it": {
"stringUnit": {
"state": "needs_review",
"value": "Quella scheda agente mostra già una chat. Riapri la sessione in un nuovo spazio di lavoro."
}
},
"ja": {
"stringUnit": {
"state": "needs_review",
"value": "そのエージェントタブにはすでにチャットが表示されています。新しいワークスペースでセッションを開き直してください。"
}
},
"km": {
"stringUnit": {
"state": "needs_review",
"value": "ផ្ទាំងភ្នាក់ងារនោះកំពុងបង្ហាញការជជែករួចហើយ។ បើកវគ្គម្ដងទៀតនៅក្នុងកន្លែងធ្វើការថ្មី។"
}
},
"ko": {
"stringUnit": {
"state": "needs_review",
"value": "그 에이전트 탭에는 이미 채팅이 표시되어 있습니다. 새 워크스페이스에서 세션을 다시 여세요."
}
},
"nb": {
"stringUnit": {
"state": "needs_review",
"value": "Den agentfanen viser allerede en chat. Åpne økten på nytt i et nytt arbeidsområde."
}
},
"pl": {
"stringUnit": {
"state": "needs_review",
"value": "Ta karta agenta już wyświetla czat. Otwórz sesję ponownie w nowym obszarze roboczym."
}
},
"pt-BR": {
"stringUnit": {
"state": "needs_review",
"value": "Essa aba de agente já mostra um chat. Abra a sessão novamente em um novo espaço de trabalho."
}
},
"ru": {
"stringUnit": {
"state": "needs_review",
"value": "Эта вкладка агента уже показывает чат. Откройте сеанс снова в новом рабочем пространстве."
}
},
"th": {
"stringUnit": {
"state": "needs_review",
"value": "แท็บเอเจนต์นั้นแสดงแชตอยู่แล้ว เปิดเซสชันอีกครั้งในพื้นที่ทำงานใหม่"
}
},
"tr": {
"stringUnit": {
"state": "needs_review",
"value": "Bu ajan sekmesi zaten bir sohbet gösteriyor. Oturumu yeni bir çalışma alanında yeniden açın."
}
},
"uk": {
"stringUnit": {
"state": "needs_review",
"value": "Ця вкладка агента вже показує чат. Відкрийте сеанс знову в новому робочому просторі."
}
},
"vi": {
"stringUnit": {
"state": "needs_review",
"value": "Thẻ tác tử đó đã hiển thị một cuộc trò chuyện. Hãy mở lại phiên trong một không gian làm việc mới."
}
},
"zh-Hans": {
"stringUnit": {
"state": "needs_review",
"value": "该代理标签页已在显示一个聊天。请在新工作区中重新打开该会话。"
}
},
"zh-Hant": {
"stringUnit": {
"state": "needs_review",
"value": "該代理分頁已在顯示一個聊天。請在新工作區中重新開啟該工作階段。"
}
}
}
},
"agentPane.adopt.unknownSession": {
"comment": "Shown when another machine's agent chat cannot open because that machine's agent service lists no chat of its own by that id; %@ is the machine's name",
"extractionState": "manual",
"localizations": {
"ar": {
"stringUnit": {
"state": "translated",
"value": "لا يشغّل %@ محادثة وكيل خاصة به بهذا المعرّف. تحقّق من معرّف الجلسة باستخدام acpmux ls على ذلك الجهاز."
}
},
"bs": {
"stringUnit": {
"state": "translated",
"value": "%@ ne pokreće takav vlastiti razgovor agenta. Provjerite ID sesije pomoću acpmux ls na toj mašini."
}
},
"da": {
"stringUnit": {
"state": "translated",
"value": "%@ kører ingen sådan agentchat selv. Tjek sessionens id med acpmux ls på den maskine."
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "%@ führt keinen solchen eigenen Agent-Chat aus. Prüfe die Sitzungs-ID mit acpmux ls auf diesem Rechner."
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "%@ runs no such agent chat of its own. Check the session id with acpmux ls on that machine."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "%@ no ejecuta ningún chat de agente propio con ese ID. Comprueba el ID de la sesión con acpmux ls en esa máquina."
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "%@ n'exécute aucune discussion d'agent avec cet identifiant. Vérifiez l'identifiant de la session avec acpmux ls sur cette machine."
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "%@ non esegue nessuna chat agente propria con quell'ID. Controlla l'ID della sessione con acpmux ls su quella macchina."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "%@ ではそのようなエージェントチャットは実行されていません。そのマシンで acpmux ls を使ってセッション ID を確認してください。"
}
},
"km": {
"stringUnit": {
"state": "translated",
"value": "%@ មិនដំណើរការការជជែកភ្នាក់ងារបែបនោះរបស់ខ្លួនទេ។ ពិនិត្យលេខសម្គាល់វគ្គដោយប្រើ acpmux ls នៅលើម៉ាស៊ីននោះ។"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "%@에서는 그런 자체 에이전트 채팅이 실행되고 있지 않습니다. 그 머신에서 acpmux ls로 세션 ID를 확인하세요."
}
},
"nb": {
"stringUnit": {
"state": "translated",
"value": "%@ kjører ingen slik egen agentchat. Sjekk økt-ID-en med acpmux ls på den maskinen."
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "%@ nie uruchamia takiego własnego czatu agenta. Sprawdź identyfikator sesji poleceniem acpmux ls na tej maszynie."
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "%@ não executa nenhum chat de agente próprio com esse ID. Confira o ID da sessão com acpmux ls nessa máquina."
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "На %@ нет такого собственного чата агента. Проверьте ID сеанса командой acpmux ls на этой машине."
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "%@ ไม่ได้เรียกใช้แชตเอเจนต์ของตัวเองที่มี ID นั้น ตรวจสอบ ID เซสชันด้วย acpmux ls บนเครื่องนั้น"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "%@ kendine ait böyle bir ajan sohbeti çalıştırmıyor. Oturum kimliğini o makinede acpmux ls ile kontrol edin."
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "На %@ немає такого власного чату агента. Перевірте ID сеансу командою acpmux ls на цій машині."
}
},
"vi": {
"stringUnit": {
"state": "translated",
"value": "%@ không chạy cuộc trò chuyện tác tử nào như vậy. Hãy kiểm tra ID phiên bằng acpmux ls trên máy đó."
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "%@ 上没有运行这样的自有代理聊天。请在那台机器上用 acpmux ls 检查会话 ID。"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "%@ 上沒有執行這樣的自有代理聊天。請在那台機器上用 acpmux ls 檢查工作階段 ID。"
}
}
}
},
"agentPane.attach.prompt": {
"extractionState": "manual",
"localizations": {
Expand Down
Loading
Loading