Skip to content
Merged
9 changes: 5 additions & 4 deletions webviews/src/agent-session/acpmux/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11549,7 +11549,8 @@ function AcpmuxPane() {
// The page's own folder is inherited (`~` in a fresh workspace): the chat starts in its start
// folder. Another project the user picked there goes through the host first.
// Use Private Folder (or Keep) there gives the typed text back to the composer.
if (cwd && cwd !== inherited) requestFolder(cwd, start, text ? () => isCurrent() && setDraft(text) : undefined);
if (cwd && cwd !== inherited)
void requestFolder(cwd, start, text ? () => isCurrent() && setDraft(text) : undefined);
else start(startFolder);
};
const loadNewTabProjects = useCallback(
Expand Down Expand Up @@ -11709,7 +11710,7 @@ function AcpmuxPane() {
onPick: chooseProject,
onBrowse: (request?: BrowseRequest) =>
callNative<{ cwd?: string }>("project.browse").then((result) => {
if (result?.cwd && !request?.deferPick) chooseProject(result.cwd);
if (result?.cwd && !request?.deferPick) void chooseProject(result.cwd);
return result?.cwd;
}),
// The chat starts in its agent-home folder, never the workspace's (AcpmuxPathPolicy).
Expand Down Expand Up @@ -12160,7 +12161,7 @@ function AcpmuxPane() {
if (!result?.cwd) return;
if (!request?.deferPick) {
setChooseFolder(false);
chooseProject(result.cwd);
void chooseProject(result.cwd);
}
return result.cwd;
})
Expand Down Expand Up @@ -12587,7 +12588,7 @@ function AcpmuxPane() {
: freshChat && !quick
? (request?: BrowseRequest) =>
callNative<{ cwd?: string }>("project.browse").then((result) => {
if (result?.cwd && !request?.deferPick) chooseProject(result.cwd);
if (result?.cwd && !request?.deferPick) void chooseProject(result.cwd);
return result?.cwd;
})
: undefined
Expand Down
61 changes: 55 additions & 6 deletions webviews/src/agent-session/acpmux/CloudStartView.gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,27 +70,76 @@ export default componentEntry<ComponentProps<typeof CloudStartView>>({
},
"checklist-clone": {
note: "A repository is being cloned into the chat folder.",
props: { start: { ...begun, hasRepository: true, repository: "manaflow-ai/cmux", machine: known.machine, machineFinishedAt: 12_400, clone: { state: "running", ...timing } } },
props: {
start: {
...begun,
hasRepository: true,
repository: "manaflow-ai/cmux",
machine: known.machine,
machineFinishedAt: 12_400,
clone: { state: "running", ...timing },
},
},
},
"checklist-setup-cached": {
note: "The environment snapshot is ready, so setup completes from cache.",
props: { start: { ...begun, hasRepository: true, repository: "manaflow-ai/cmux", clone: { state: "done", ...timing }, setup: { state: "done", status: "ready", ...timing } } },
props: {
start: {
...begun,
hasRepository: true,
repository: "manaflow-ai/cmux",
clone: { state: "done", ...timing },
setup: { state: "done", status: "ready", ...timing },
},
},
},
"checklist-setup-running": {
note: "The derived environment is still building its setup snapshot.",
props: { start: { ...begun, hasRepository: true, repository: "manaflow-ai/cmux", clone: { state: "done", ...timing }, setup: { state: "running", status: "building", startedAt: 10_000 } } },
props: {
start: {
...begun,
hasRepository: true,
repository: "manaflow-ai/cmux",
clone: { state: "done", ...timing },
setup: { state: "running", status: "building", startedAt: 10_000 },
},
},
},
"checklist-setup-failed": {
note: "Setup failed and the card keeps the short log tail inline.",
props: { start: { ...begun, hasRepository: true, repository: "manaflow-ai/cmux", clone: { state: "done", ...timing }, setup: { state: "failed", status: "failed", logTail: "npm ERR! missing script: build", ...timing } } },
props: {
start: {
...begun,
hasRepository: true,
repository: "manaflow-ai/cmux",
clone: { state: "done", ...timing },
setup: { state: "failed", status: "failed", logTail: "npm ERR! missing script: build", ...timing },
},
},
},
"checklist-setup-skipped": {
note: "No setup script is configured; the link opens Edit cloud environment at Script.",
props: { start: { ...begun, hasRepository: true, repository: "manaflow-ai/cmux", clone: { state: "done", ...timing }, setup: { state: "skipped", hasScript: false } } },
props: {
start: {
...begun,
hasRepository: true,
repository: "manaflow-ai/cmux",
clone: { state: "done", ...timing },
setup: { state: "skipped", hasScript: false },
},
},
},
initialized: {
note: "Once the harness starts, the checklist folds to one quiet line.",
props: { start: { ...begun, initialized: true, agent: "Claude Code", harnessStartedAt: 12_400, harnessFinishedAt: 12_900 } },
props: {
start: {
...begun,
initialized: true,
agent: "Claude Code",
harnessStartedAt: 12_400,
harnessFinishedAt: 12_900,
},
},
},
},
// The gallery loads no experiment without a step (format.ts validateEntries): the card is static,
Expand Down
39 changes: 34 additions & 5 deletions webviews/src/agent-session/acpmux/CloudStartView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ test("a failure says the host's words with no spinner, and the New Tab page draw
});

test("the startup checklist skips clone without a repository and explains a missing script", () => {
const rows = startupChecklist({ key: "k", task: "hi", state: "starting", home: true, setup: { state: "skipped", hasScript: false } });
const rows = startupChecklist({
key: "k",
task: "hi",
state: "starting",
home: true,
setup: { state: "skipped", hasScript: false },
});
expect(rows.map((row) => row.status)).toEqual(["pending", "skipped", "skipped", "pending"]);
expect(rows[2]?.action).toBe(true);
expect(rows[2]?.detail).toContain("Add a setup script");
Expand All @@ -66,22 +72,45 @@ test("environment setup status maps cached, running and failed states", () => {
const base = { key: "k", task: "hi", state: "starting" as const };
expect(startupChecklist({ ...base, setup: { state: "done", status: "ready" } })[2]?.detail).toBe("cached");
expect(startupChecklist({ ...base, setup: { state: "running", status: "building" } })[2]?.status).toBe("running");
expect(startupChecklist({ ...base, setup: { state: "failed", status: "failed", logTail: "npm ERR!" } })[2]).toMatchObject({ status: "error", logTail: "npm ERR!" });
expect(
startupChecklist({ ...base, setup: { state: "failed", status: "failed", logTail: "npm ERR!" } })[2],
).toMatchObject({ status: "error", logTail: "npm ERR!" });
});

test("clone status keeps seed phase and object progress in the row", () => {
const rows = startupChecklist({ key: "k", task: "hi", state: "starting", hasRepository: true, clone: { state: "running", phase: "Receiving objects", progress: { objects: 2, totalObjects: 4, receivedBytes: 2048 } } });
const rows = startupChecklist({
key: "k",
task: "hi",
state: "starting",
hasRepository: true,
clone: {
state: "running",
phase: "Receiving objects",
progress: { objects: 2, totalObjects: 4, receivedBytes: 2048 },
},
});
expect(rows[1]?.detail).toBe("Receiving objects · 2/4 objects · 2 KB");
});

test("a machine failure stops a clone spinner", () => {
const rows = startupChecklist({ key: "k", task: "hi", state: "failed", step: "machine", hasRepository: true, clone: { state: "running" } });
const rows = startupChecklist({
key: "k",
task: "hi",
state: "failed",
step: "machine",
hasRepository: true,
clone: { state: "running" },
});
expect(rows[0]?.status).toBe("error");
expect(rows[1]?.status).toBe("pending");
});

test("a missing setup script is plain text without an environment callback", () => {
const html = renderToStaticMarkup(<CloudStartView start={{ key: "k", task: "hi", state: "starting", home: true, setup: { state: "skipped", hasScript: false } }} />);
const html = renderToStaticMarkup(
<CloudStartView
start={{ key: "k", task: "hi", state: "starting", home: true, setup: { state: "skipped", hasScript: false } }}
/>,
);
expect(html).toContain("Add a setup script to install dependencies and configure your environment.");
expect(html).not.toContain('class="acpmux-cloud-start__link"');
});
Loading
Loading