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
5 changes: 5 additions & 0 deletions packages/harness-desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ map/history bytes unchanged. The report includes raw/gzip worker size and cold/
warm UI readiness timings; a screenshot is saved beside the smoke report.
Run with `CI` unset on a developer machine to use the runner's temporary profile.

The same smoke run verifies boot-token protection for Assistant history actions,
repeated End of a real Terminal, retained paused lifecycle metadata, and rejection
of ordinary Assistant attachment after End. These checks need no model access.
Activated accepted-context and signed installed release checks remain separate.

## Coding-agent updates

On each normal launch, Studio checks installed Claude Code and Codex against npm
Expand Down
25 changes: 24 additions & 1 deletion packages/harness-desktop/src/main/smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,30 @@ async function checkSessionCreate(
}

const inherited = await checkAgentEnvironment(session.id);
return `spawned a session in ${path.basename(cwd)} (status ${found.status ?? session.status ?? "?"}, ready via SessionStart hook); ${inherited}`;
// Exercise the same persisted End and action contracts in the packaged
// shared server. This needs no Assistant grant or model/provider access.
const headers = { "X-Harness-Token": token, "content-type": "application/json" };
for (const action of ["inspect", "resume", "continue"]) {
const route = `${base}/api/sessions/${session.id}/assistant/${action}`;
const rejected = await fetch(route, { method: "POST", headers, body: "{}" });
if (rejected.status !== 400) throw new Error(`Packaged ${action} contract missing (${rejected.status})`);
const unauthorized = await fetch(route, { method: "POST", headers: { "content-type": "application/json" }, body: "{}" });
if (unauthorized.status !== 401) throw new Error(`Packaged ${action} accepted missing boot token`);
}
for (let attempt = 0; attempt < 2; attempt++) {
const ended = await fetch(`${base}/api/sessions/${session.id}`, { method: "DELETE", headers });
if (!ended.ok) throw new Error(`Packaged End cleanup unconfirmed (${ended.status})`);
}
const descriptor = JSON.parse(await fetchOk(`${base}/opencode/${session.id}/lifecycle`, token, 200));
if (descriptor.lifecycle !== "ended" || descriptor.execution !== "paused")
throw new Error("Packaged End did not retain paused lifecycle metadata");
found = (await readState()).sessions?.find(s => s.id === session.id);
if (found?.status !== "exited") throw new Error("Packaged End lost the retained session row");
const attach = await fetch(`${base}/opencode/${session.id}/attach`, {
method: "POST", headers, body: JSON.stringify({ expectedRevision: descriptor.revision }),
});
if (attach.status !== 409) throw new Error("Packaged ended session accepted ordinary Attach");
return `spawned and ended a retained session in ${path.basename(cwd)} (ready via SessionStart hook); lifecycle actions authenticated; ${inherited}`;
} finally {
// Best-effort ONLY, and deliberately so: this directory is the live pty's
// cwd, and Windows refuses to delete a directory that is a running
Expand Down