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
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ public struct LayoutMapping {
case .top: .top
case .bottom: .bottom
}
return DockColumn(edge: edge, mode: snapshot.mode == .overlay ? .overlay : .docked)
return DockColumn(edge: edge, mode: snapshot.mode == .overlay ? .overlay : .docked,
role: snapshot.role.map { _ in .agentChat })
}

/// The layout's dock as the daemon's value. Top and bottom need a daemon
Expand All @@ -91,7 +92,8 @@ public struct LayoutMapping {
case .top: .top
case .bottom: .bottom
}
return DockSnapshot(edge: edge, mode: dock.mode == .overlay ? .overlay : .docked)
return DockSnapshot(edge: edge, mode: dock.mode == .overlay ? .overlay : .docked,
role: dock.role.map { _ in .agentChat })
}

/// Converts one daemon layout node. Nil when nothing in it can be shown.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ public struct DaemonCapabilities: Sendable {
/// accept edges `top` and `bottom`, sent back as `columns[].dock`
/// (plans/cmux-next/layout-model.md).
public let edgeDocks = "edge-docks-v1"
/// `dock.role` (`agent_chat`) on `set-column-dock`, `move-tab-to-column`
/// and `columns[].dock`: the agent chat column.
public let dockColumnRole = "dock-column-role-v1"
/// Rows: `new-row`, `set-row-heights` and `columns[].rows`
/// (plans/cmux-next/rows.md). Without it no row op is sent.
public let rows = "rows-v1"
Expand Down Expand Up @@ -232,7 +235,7 @@ public struct DaemonCapabilities: Sendable {
terminalReap, terminalReaperActive, batchClose, closeReason, browserHostProvider, frontendBrowserActivate, frontendBrowserInsertAfter, loopbackForward, screenMetadata, screenGroups, profiles,
terminalPendingSequence, personalTerminals, browserProfiles, notificationSource,
terminalShellArgs, terminalFrontendShellIntegration, launchSnapshot, bookmarks, workspacePin, notificationMarkUnread,
terminalCommandJournal, dockColumns, edgeDocks, rows, tabColumnRespawn, endTerminalsKeepLayout, stateResources,
terminalCommandJournal, dockColumns, edgeDocks, dockColumnRole, rows, tabColumnRespawn, endTerminalsKeepLayout, stateResources,
sessionIdentity, localConversations, tabSplitRespawn, frontendBrowserHistory,
attachIdentity, creationReceipts, creationAttemptKeys, terminalColorOverrides,
workspaceKind, workspaceAgentFolder, conversationTabs, agentSessionTabs, pageTabs, conversationSearch, cloudConversations, localAttachments,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Foundation
/// Pins the column holding `pane` to a viewport edge, or unpins it
/// (`dock-columns-v1`). One docked column per edge: the daemon unpins a
/// column already on that edge, and refuses to leave no column scrolling.
/// `role` needs `dock-column-role-v1`; an older daemon ignores it.
public struct SetColumnDockRequest: DaemonRequest {
public typealias Response = EmptyResponse
public static let command = "set-column-dock"
Expand All @@ -11,12 +12,14 @@ public struct SetColumnDockRequest: DaemonRequest {
public var dock: Bool
public var edge: String?
public var mode: String?
public var role: String?
public var transaction: UInt64?
public init(pane: PaneID, dock: DockSnapshot?, transaction: UInt64? = nil) {
self.pane = pane
self.dock = dock != nil
self.edge = dock?.edge.rawValue
self.mode = dock?.mode.rawValue
self.role = dock?.role?.rawValue
self.transaction = transaction
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public struct MoveTabToColumnRequest: DaemonRequest {
}

enum CodingKeys: String, CodingKey { case surface, pane, screen, afterColumn, width, dock, transaction }
enum PinKeys: String, CodingKey { case edge, mode }
enum PinKeys: String, CodingKey { case edge, mode, role }
public func encode(to encoder: any Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(surface, forKey: .surface)
Expand All @@ -65,6 +65,7 @@ public struct MoveTabToColumnRequest: DaemonRequest {
var pin = c.nestedContainer(keyedBy: PinKeys.self, forKey: .dock)
try pin.encode(dock.edge.rawValue, forKey: .edge)
try pin.encode(dock.mode.rawValue, forKey: .mode)
try pin.encodeIfPresent(dock.role?.rawValue, forKey: .role)
}
try c.encodeIfPresent(transaction, forKey: .transaction)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,26 @@ public struct DockSnapshot: Sendable, Hashable, Decodable {
public var isBand: Bool { self == .top || self == .bottom }
}
public enum Mode: String, Sendable, Hashable, Decodable { case docked, overlay }
/// What the column is for (`dock-column-role-v1`): the agent chat column.
public enum Role: String, Sendable, Hashable, Decodable { case agentChat = "agent_chat" }
public var edge: Edge
public var mode: Mode
public var role: Role?

public init(edge: Edge, mode: Mode) {
public init(edge: Edge, mode: Mode, role: Role? = nil) {
self.edge = edge
self.mode = mode
self.role = role
}

enum CodingKeys: String, CodingKey { case edge, mode }
enum CodingKeys: String, CodingKey { case edge, mode, role }

/// Unknown values from a newer daemon fall back to the defaults
/// (right, docked) rather than dropping the screen.
/// (right, docked, no role) rather than dropping the screen.
public init(from decoder: any Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
edge = (try? c.decodeIfPresent(String.self, forKey: .edge)).flatMap { $0.flatMap(Edge.init(rawValue:)) } ?? .right
mode = (try? c.decodeIfPresent(String.self, forKey: .mode)).flatMap { $0.flatMap(Mode.init(rawValue:)) } ?? .docked
role = (try? c.decodeIfPresent(String.self, forKey: .role)).flatMap { $0.flatMap(Role.init(rawValue:)) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ public nonisolated enum DockMode: String, Hashable, Sendable, CaseIterable {
public nonisolated struct DockColumn: Hashable, Sendable {
public var edge: DockEdge
public var mode: DockMode
public var role: DockRole?

public init(edge: DockEdge = .right, mode: DockMode = .docked) {
public init(edge: DockEdge = .right, mode: DockMode = .docked, role: DockRole? = nil) {
self.edge = edge
self.mode = mode
self.role = role
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// What a docked column is for (daemon `dock.role`, `dock-column-role-v1`).
public nonisolated enum DockRole: String, Hashable, Sendable {
/// The agent chat column: new terminals and browsers open beside it,
/// never in it (lawrence-call-1006 D).
case agentChat
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ struct DockColumnMappingTests {
#expect(dock == DockSnapshot(edge: .right, mode: .docked))
}

/// `dock-column-role-v1`: the agent chat role survives the trip through
/// the layout model and back to the daemon; an unknown role is dropped.
@Test func theAgentChatRoleSurvivesTheLayout() throws {
func role(_ json: String) throws -> String? {
let dock = try #require(try column(#"{"id":9,"width":0.3,"layout":{"type":"leaf","pane":4},"dock":"# + json + "}").dock)
let request = SetColumnDockRequest(pane: 4, dock: LayoutMapping.snapshot(LayoutMapping.dock(dock)))
let sent = try JSONSerialization.jsonObject(with: JSONEncoder().encode(request)) as? [String: Any]
return sent?["role"] as? String
}
#expect(try role(#"{"edge":"left","mode":"docked","role":"agent_chat"}"#) == "agent_chat")
#expect(try role(#"{"edge":"left","mode":"docked","role":"notes"}"#) == nil)
#expect(try role(#"{"edge":"left","mode":"docked"}"#) == nil)
}

@Test func roundTripsThroughTheLayout() {
for edge in DockEdge.allCases {
for mode in DockMode.allCases {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@ import Testing
#expect(unpin["sticky"] == nil)
}

/// `dock-column-role-v1`: an agent chat dock read from the daemon is sent
/// back with its role; a dock without one sends none.
@Test func dockRequestsCarryTheDockRole() throws {
let chat = try JSONDecoder().decode(DockSnapshot.self, from: Data(#"{"edge":"left","mode":"docked","role":"agent_chat"}"#.utf8))
#expect(try object(SetColumnDockRequest(pane: 4, dock: chat))["role"] == .string("agent_chat"))
let move = try object(MoveTabToColumnRequest(surface: 3, target: .pane(7), dock: chat))
#expect(move["dock"]?["role"] == .string("agent_chat"))
let plain = DockSnapshot(edge: .left, mode: .docked)
#expect(try object(SetColumnDockRequest(pane: 4, dock: plain))["role"] == nil)
#expect(try object(MoveTabToColumnRequest(surface: 3, target: .pane(7), dock: plain))["dock"]?["role"] == nil)
}

@Test func tabDragCommandsCarryTransaction() throws {
let split = try object(MoveTabToSplitRequest(surface: 3, pane: 4, edge: .left, transaction: "tx"))
#expect(split["cmd"] == .string("move-tab-to-split"))
Expand Down
10 changes: 5 additions & 5 deletions cmux-tui/bindings/cpp/.cmux-sdk-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@
},
{
"path": "include/cmux/raw/generated/models.hpp",
"sha256": "6dc19eb321542cc906c5d1717d260a8f3d44405e4731fc1771bfe40137fd8c6f",
"size": 251195
"sha256": "1206a6913831c970778171c13f4ffc26002a93ab836f51fa08b8fddb817644f1",
"size": 251257
},
{
"path": "src/raw/generated/protocol.cpp",
"sha256": "6a0162c9b3645e6064d23a17bc83c1469643bf7378af7f1cc8811d46b45b1525",
"size": 1602616
"sha256": "e0a7c1eb983bf5cd3a40a1246841e738798df9e5c78afb1c07e68b804c814888",
"size": 1603978
}
],
"format": 1,
"ir_sha256": "569b470215beb2ce9523c30121918b781b0e9c11d5c77bb762fd006b813faf5b",
"ir_sha256": "a12acc481313eced97f8c489168cac258fd1b9c8b5b4fde33476bcaeaaf0e11b",
"language": "cpp",
"mux_protocol": 12,
"schema_version": 2
Expand Down
4 changes: 3 additions & 1 deletion cmux-tui/bindings/cpp/include/cmux/raw/generated/models.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace cmux::raw {

inline constexpr std::uint32_t kMuxProtocolVersion = 12U;
inline constexpr std::string_view kProtocolIrSha256 = "569b470215beb2ce9523c30121918b781b0e9c11d5c77bb762fd006b813faf5b";
inline constexpr std::string_view kProtocolIrSha256 = "a12acc481313eced97f8c489168cac258fd1b9c8b5b4fde33476bcaeaaf0e11b";

struct ActivitySnapshot;
struct ActivitySubscribeResult;
Expand Down Expand Up @@ -1493,6 +1493,7 @@ struct ColorsChangedEvent {
struct ColumnPin {
std::string edge{};
std::string mode{};
Field<std::string> role{};
friend bool operator==(const ColumnPin&, const ColumnPin&) = default;
};

Expand Down Expand Up @@ -3963,6 +3964,7 @@ struct SetColumnDockRequest {
Field<std::string> mode{};
Id pane{};
Field<bool> permanent{};
Field<std::string> role{};
Field<std::uint64_t> transaction{};
friend bool operator==(const SetColumnDockRequest&, const SetColumnDockRequest&) = default;
};
Expand Down
35 changes: 34 additions & 1 deletion cmux-tui/bindings/cpp/src/raw/generated/protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,11 @@ Result<Json> Codec<ColumnPin>::encode(const ColumnPin& value) {
auto encoded_mode = encode_value(value.mode);
if (!encoded_mode) return std::move(encoded_mode).error();
object.emplace("mode", std::move(encoded_mode).value());
if (!value.role.is_absent()) {
auto encoded = encode_value(value.role);
if (!encoded) return std::move(encoded).error();
object.emplace("role", std::move(encoded).value());
}
return Json(std::move(object));
}

Expand All @@ -1595,6 +1600,16 @@ Result<ColumnPin> Codec<ColumnPin>::decode(const Json& value) {
if (!decoded) return std::move(decoded).error();
result.mode = std::move(decoded).value();
}
const Json* field_role = value.find("role");
if (field_role) {
if (field_role->is_null()) {
result.role = Field<std::string>::null();
} else {
auto decoded = decode_value<std::string>(*field_role);
if (!decoded) return std::move(decoded).error();
result.role = Field<std::string>(std::move(decoded).value());
}
}
return result;
}

Expand Down Expand Up @@ -23735,6 +23750,11 @@ Result<Json> Codec<SetColumnDockRequest>::encode(const SetColumnDockRequest& val
if (!encoded) return std::move(encoded).error();
object.emplace("permanent", std::move(encoded).value());
}
if (!value.role.is_absent()) {
auto encoded = encode_value(value.role);
if (!encoded) return std::move(encoded).error();
object.emplace("role", std::move(encoded).value());
}
if (!value.transaction.is_absent()) {
auto encoded = encode_value(value.transaction);
if (!encoded) return std::move(encoded).error();
Expand Down Expand Up @@ -23795,6 +23815,16 @@ Result<SetColumnDockRequest> Codec<SetColumnDockRequest>::decode(const Json& val
result.permanent = Field<bool>(std::move(decoded).value());
}
}
const Json* field_role = value.find("role");
if (field_role) {
if (field_role->is_null()) {
result.role = Field<std::string>::null();
} else {
auto decoded = decode_value<std::string>(*field_role);
if (!decoded) return std::move(decoded).error();
result.role = Field<std::string>(std::move(decoded).value());
}
}
const Json* field_transaction = value.find("transaction");
if (field_transaction) {
if (field_transaction->is_null()) {
Expand Down Expand Up @@ -33724,6 +33754,9 @@ constexpr std::array<CommandFieldRequirement, 5> kCommand175FieldRequirements{{
{"display_name", 12U, "shared-sizing-v1"},
{"user_id", 12U, "shared-sizing-v1"},
}};
constexpr std::array<CommandFieldRequirement, 1> kCommand177FieldRequirements{{
{"role", 12U, "dock-column-role-v1"},
}};
constexpr std::array<CommandFieldRequirement, 7> kCommand178FieldRequirements{{
{"complete", 9U, ""},
{"cursor", 9U, ""},
Expand Down Expand Up @@ -33939,7 +33972,7 @@ constexpr std::array<CommandMetadata, 232> kCommands{{
{"set-cell-pixels", "frontend", 6U, "", false, "", "", std::span<const CommandFieldRequirement>{}},
{"set-client-info", "control", 6U, "", false, "", "", std::span<const CommandFieldRequirement>(kCommand175FieldRequirements)},
{"set-client-sizing", "control", 10U, "", false, "", "", std::span<const CommandFieldRequirement>{}},
{"set-column-dock", "control", 12U, "dock-columns-v1", false, "", "", std::span<const CommandFieldRequirement>{}},
{"set-column-dock", "control", 12U, "dock-columns-v1", false, "", "", std::span<const CommandFieldRequirement>(kCommand177FieldRequirements)},
{"set-default-colors", "control", 5U, "", false, "", "", std::span<const CommandFieldRequirement>(kCommand178FieldRequirements)},
{"set-frontend-browser-history", "control", 12U, "frontend-browser-history-v1", false, "", "", std::span<const CommandFieldRequirement>{}},
{"set-personal-terminal", "control", 12U, "personal-terminals-v1", false, "", "", std::span<const CommandFieldRequirement>{}},
Expand Down
20 changes: 10 additions & 10 deletions cmux-tui/bindings/go/raw/.cmux-sdk-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,32 @@
"files": [
{
"path": "generated_commands.go",
"sha256": "600e3c82fdb975cb8f8d1bd02c0f8adf2bb346398f21a0c26dd9d0e99e3308f0",
"size": 986822
"sha256": "f08ff50b319f19d1e1205eb03983506016ff70198f0134b7c01f45f96876c84d",
"size": 987752
},
{
"path": "generated_events.go",
"sha256": "e6e18cf37ca2b35aa1df836824a0d19f16e3fa068aebbccf769ede1105af6345",
"sha256": "332d244d141e17e52fd0f284470527ae787a47637fe8ebc6bdaf3b8533417d84",
"size": 157538
},
{
"path": "generated_metadata.go",
"sha256": "108c2c6d7e3701be46c8fc40521fe1a5e08c9ef0d8bb8559b9eca2a2b908e660",
"size": 99289
"sha256": "e3b6da15ae106ff1834cb56d3980d6f1f57302195c374037e08824e9a6bbc043",
"size": 99360
},
{
"path": "generated_presence_test.go",
"sha256": "85e7c7221b10a527c88cbcc5079ed5ca5eff24ab39dfbe93e5922942681bc241",
"size": 2097061
"sha256": "ab69a6d2bfc4b435c78a4ada9270f298fe4c8ebbe99477507d7e22e2ee1b8cc8",
"size": 2099928
},
{
"path": "generated_types.go",
"sha256": "af4cd1ed47df7b6a5c70eb62378fc8ed79387103a8816441f07ba948e389c5b3",
"size": 441807
"sha256": "cb35940836f8efe3890ec3f35d95be8cabaab38ad89553e964aae4dcbe31542e",
"size": 442592
}
],
"format": 1,
"ir_sha256": "569b470215beb2ce9523c30121918b781b0e9c11d5c77bb762fd006b813faf5b",
"ir_sha256": "a12acc481313eced97f8c489168cac258fd1b9c8b5b4fde33476bcaeaaf0e11b",
"language": "go",
"mux_protocol": 12,
"schema_version": 2
Expand Down
Loading
Loading