Skip to content
Closed
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
2 changes: 1 addition & 1 deletion webviews/scripts/design-tokens-baseline.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
{"file":"agent-session/acpmux/composerControls.css","kind":"height","property":"height","values":{"1px":1,"calc(23.5px * var(--cmux-interface-scale, 1))":1,"calc(15px * var(--cmux-interface-scale, 1))":1,"3px":1,"16px":1}}
{"file":"agent-session/acpmux/composerControls.css","kind":"spacing","property":"gap","values":{"6px":1}}
{"file":"agent-session/acpmux/composerControls.css","kind":"spacing","property":"padding","values":{"0 8px 6px":1,"6px 10px 4px":1,"0 6px 0 8px":1}}
{"file":"agent-session/acpmux/composerLocation.css","kind":"height","property":"height","values":{"46px":1,"calc(46px + var(--acpmux-strip-gap, 4px))":2,"22px":3,"1px":3,"12px":1,"16px":3,"30px":1,"6px":1}}
{"file":"agent-session/acpmux/composerLocation.css","kind":"height","property":"height","values":{"46px":1,"calc(46px + var(--acpmux-strip-gap, 4px))":2,"22px":3,"1px":2,"12px":1,"16px":3,"30px":1}}
{"file":"agent-session/acpmux/composerLocation.css","kind":"height","property":"min-height","values":{"52px":1,"16px":1,"44px":1}}
{"file":"agent-session/acpmux/composerLocation.css","kind":"spacing","property":"gap","values":{"var(--acpmux-location-gap, 6px)":2,"1px":1,"6px":1,"3px":1}}
{"file":"agent-session/acpmux/composerLocation.css","kind":"spacing","property":"padding","values":{"0 var(--acpmux-strip-inset, 3px)":1,"0 6px":2,"0 3px":1,"5px 4px":1,"4px 4px 2px":1,"0 5px":1,"2px 8px 2px 10px":1,"0 calc(var(--acpmux-top-strip-edge) - var(--acpmux-tray-inset) - 6px) var(--acpmux-tray-tuck)":1,"0 0 calc(2px + var(--acpmux-tray-tuck)) calc(var(--acpmux-top-strip-edge) - var(--acpmux-tray-inset))":1,"6px 8px":1,"1px 4px":1,"calc(11px * var(--cmux-interface-scale, 1)) max(0px, calc(8px * var(--cmux-interface-scale, 1) - var(--cmux-scroller-size, 8px))) calc(11px * var(--cmux-interface-scale, 1)) calc(7px * var(--cmux-interface-scale, 1))":1,"11px 8px 11px 23px":1}}
Expand Down
53 changes: 52 additions & 1 deletion webviews/src/agent-session/acpmux/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { cloudEnvironments } from "./machineInventory";
import React, {
lazy,
memo,
Expand Down Expand Up @@ -5680,6 +5681,53 @@ function AcpmuxPane() {
// SSH… opens Connect to Machine; New Cloud Machine opens the host's create flow (Lawrence 2026-10-06).
const connectMachine = (kind: ConnectKind) =>
void callNative("action.run", { id: CONNECT_ACTIONS[kind] }).catch(() => undefined);
const [cloudMachines, setCloudMachines] = useState<import("./environments").Environment[]>([]);
const refreshCloudMachines = () => {
void callNative("machine.list", {}).then(
(reply) => setCloudMachines(cloudEnvironments(reply)),
() => undefined,
);
};
useEffect(() => {
if (!handshaken) return;
let active = true;
void callNative("machine.list", {}).then(
(reply) => {
if (active) setCloudMachines(cloudEnvironments(reply));
},
() => undefined,
);
return () => {
active = false;
};
}, [handshaken]);
const setupDocs = () =>
void callNative("tab.open", {
kind: "browser",
text: "https://github.com/manaflow-ai/cmux/blob/feat-cmux-next/docs/remote-agents-and-cloud.md",
}).catch(() => undefined);
const machineAction = (
machine: string,
action: "resume" | "update" | "login" | "open",
_kind: import("./environments").EnvironmentKind,
native = false,
) => {
if (
(action === "resume" || action === "open") &&
(!native || !cloudMachines.some((row) => row.id === machine && row.native))
) {
setupDocs();
return;
}
if (action === "update" || action === "login") {
setupDocs();
return;
}
void callNative(action === "resume" ? "machine.resume" : "machine.open", { machine }).then(
refreshCloudMachines,
(error: unknown) => directClient.current?.notice(errorMessage(error)),
);
};
// Move to Cloud and Bring Back Local report their own progress and failures as the app's toasts;
// the notice says only that the app did not start the move (turned off, or not for this chat).
const offloadChat = (kind: OffloadKind) =>
Expand Down Expand Up @@ -8815,7 +8863,7 @@ function AcpmuxPane() {
{/* An attached image opens in the chat's image viewer, as a transcript image does. */}
<ImageViewerContext.Provider value={openImage}>
<Composer
snapshot={composerSnapshot}
snapshot={{ ...composerSnapshot, machines: cloudMachines }}
headerCwd={snapshot.summary?.cwd ?? ""}
sessionId={snapshot.sessionId ?? snapshot.summary?.sessionId}
placeholder={isSideSession ? t("sideSession.placeholder") : undefined}
Expand Down Expand Up @@ -9020,11 +9068,14 @@ function AcpmuxPane() {
onProject={chooseProject}
onNewWorktree={freshChat && !quick && !remoteChat ? startInWorktree : undefined}
onConnect={connectMachine}
onMachineAction={machineAction}
onSetupDocs={setupDocs}
// The floating chat has no tab to move.
{...(quick ? {} : { onOffload: offloadChat, offloads })}
// The list opened: the machines' status and the chat's moves are read again.
onRefreshMachines={() => {
void directClient.current?.refreshPeers();
refreshCloudMachines();
refreshTabState();
}}
// A started local chat lists the same folders to move to (dogfood 09); Start Agent's
Expand Down
15 changes: 14 additions & 1 deletion webviews/src/agent-session/acpmux/Composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useImperativeHandle, useLayoutEffect, us
import { createPortal } from "react-dom";
// Send's iMessage colors (--cmux-messages-blue); build-agent-pane-web.sh inlines the sheet.
import "../../theme/messages.css";
import type { ConnectKind, OffloadKind } from "./environments";
import type { ConnectKind, EnvironmentKind, OffloadKind } from "./environments";
import { useEscapingList } from "../../ui/popupHost";
import type { AcpmuxSnapshot, QueueEdit } from "./model";
import { type AttachmentError, type ComposerAttachment, type DroppedKind, type LinkItem } from "./attachments";
Expand Down Expand Up @@ -256,6 +256,13 @@ type Props = {
offloads?: readonly OffloadKind[];
/// The environment picker opened: read the machines and their status again.
onRefreshMachines?(): void;
onMachineAction?(
machine: string,
action: "resume" | "update" | "login" | "open",
kind: EnvironmentKind,
native?: boolean,
): void;
onSetupDocs?(): void;
/// This Mac's name for the location row.
localName?: string;
/// Preview features on: the location row's computer is the environment picker (ComposerContext).
Expand Down Expand Up @@ -328,6 +335,8 @@ export function Composer({
onOffload,
offloads,
onRefreshMachines,
onMachineAction,
onSetupDocs,
localName,
environments,
onNewChat,
Expand Down Expand Up @@ -1496,7 +1505,11 @@ export function Composer({
peers={snapshot.peers}
{...(snapshot.peerKinds ? { peerKinds: snapshot.peerKinds } : {})}
{...(snapshot.peerConnected ? { peerConnected: snapshot.peerConnected } : {})}
{...(snapshot.peerStatus ? { peerStatus: snapshot.peerStatus } : {})}
{...(snapshot.machines ? { machines: snapshot.machines } : {})}
{...(onRefreshMachines ? { onRefreshMachines } : {})}
{...(onMachineAction ? { onMachineAction } : {})}
{...(onSetupDocs ? { onSetupDocs } : {})}
environments={environments}
onProject={
onProject &&
Expand Down
57 changes: 57 additions & 0 deletions webviews/src/agent-session/acpmux/ComposerContext.gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,63 @@ export default componentEntry<ComponentProps<typeof ComposerContext>>({
props: baseProps,
play: openComputer,
},
"environment-status-matrix": {
note: "Machine picker status matrix: connected, connecting, paused with Resume, offline, unreachable, needs update and needs login are rendered only from daemon status; the footer keeps cloud creation, SSH connection and ticket pairing actions together.",
props: {
...baseProps,
environments: true,
peers: ["gpu-box", "build-mini", "Cloud machine", "offline-box", "ticket-box"],
peerKinds: {
"gpu-box": "ssh",
"build-mini": "ssh",
"Cloud machine": "cloud",
"offline-box": "ssh",
"ticket-box": "cloud",
},
peerConnected: {
"gpu-box": true,
"build-mini": false,
"Cloud machine": true,
"offline-box": false,
"ticket-box": false,
},
peerStatus: {
"gpu-box": { state: "connected", connected: true },
"build-mini": { state: "connecting", connected: false },
"Cloud machine": { state: "paused", connected: false },
"offline-box": { state: "offline", connected: false },
"ticket-box": { state: "unreachable", connected: false, error: "peer unreachable" },
},
onMachineAction: () => undefined,
},
play: openComputer,
},
"environment-needs-update": {
note: "An outdated peer's explicit daemon marker reads Needs update and offers Update without guessing from its name or version.",
props: {
...baseProps,
environments: true,
peers: ["old-box"],
peerKinds: { "old-box": "ssh" },
peerConnected: { "old-box": true },
peerStatus: { "old-box": { state: "connected", connected: true, outdated: true } },
onMachineAction: () => undefined,
},
play: openComputer,
},
"environment-needs-login": {
note: "A machine that explicitly needs login stays visible with a Log in action; the picker never treats a missing status as login required.",
props: {
...baseProps,
environments: true,
peers: ["signed-out"],
peerKinds: { "signed-out": "ssh" },
peerConnected: { "signed-out": false },
peerStatus: { "signed-out": { state: "offline", connected: false, needsLogin: true } },
onMachineAction: () => undefined,
},
play: openComputer,
},
"environment-picker": {
note: "Preview: the computer is the environment picker, one searchable list in Local, SSH and Cloud sections with ⌥⌘1–9; New Cloud Machine ends the Cloud section and New environment (SSH…) comes last. gpu-box is connected (a filled dot), Cloud machine is not (a ring); this Mac has no status.",
props: {
Expand Down
Loading
Loading