fix(server-mode): a session-only share resumes in a free slot only

A start that names no slot means pane 0; for a user with only a session of
the workspace that was let through and answered ERUNLIVE whenever another
user's agent held pane 0 (found by the browser run). The client already
sends slot: "auto"; the scope now requires it.
This commit is contained in:
2026-11-06 11:45:25 +07:00
parent 18e574a765
commit dea74b777e
2 changed files with 9 additions and 4 deletions
+6 -2
View File
@@ -479,6 +479,7 @@ describe("sharing one session", () => {
});
await refused("POST", "/api/run", { laneId: laneS, initialPrompt: "hi" });
await refused("POST", `/api/lanes/${laneS}/start`, { resumeSessionId: "s1-shared", slot: 0 });
await refused("POST", `/api/lanes/${laneS}/start`, { resumeSessionId: "s1-shared" });
await refused("GET", `/api/lanes/${laneS}/git/tree?source=1`);
await refused("GET", `/api/artifacts/candidates?laneId=${laneS}`);
await refused("PATCH", "/api/sessions/s1-shared", { name: "mine now" });
@@ -494,7 +495,10 @@ describe("sharing one session", () => {
db.prepare("INSERT INTO session_shares (session_id, user_id) VALUES ('sT-other', ?)").run(
users.carol.id
);
await refused("POST", `/api/lanes/${laneT}/start`, { resumeSessionId: "s1-shared" });
await refused("POST", `/api/lanes/${laneT}/start`, {
resumeSessionId: "s1-shared",
slot: "auto",
});
db.prepare("DELETE FROM session_shares WHERE session_id = 'sT-other'").run();
// Another session of it is not even there.
const other = await call("POST", `/api/lanes/${laneS}/start`, {
@@ -505,7 +509,7 @@ describe("sharing one session", () => {
// A resume of the shared one gets past the scope (and then needs their own account).
const resume = await call("POST", `/api/lanes/${laneS}/start`, {
as: "carol",
body: { resumeSessionId: "s1-shared", prompt: "hi" },
body: { resumeSessionId: "s1-shared", prompt: "hi", slot: "auto" },
});
assert.ok(![403, 404].includes(resume.status), JSON.stringify(resume.body));
// Reads of the workspace's git and its own row pass.
+3 -2
View File
@@ -399,8 +399,9 @@ function sessionShareAllows(req, rest, laneId) {
b.forkSession !== true &&
b.shell !== true &&
b.resume !== true &&
// A free slot the server picks: pane 0 is the workspace's own run.
(b.slot === undefined || b.slot === "auto") &&
// A free slot the server picks (no slot means pane 0, the workspace's
// own run — another user's live agent there would answer ERUNLIVE).
b.slot === "auto" &&
sessionSharedWith(b.resumeSessionId, memberId(req)) &&
Boolean(
sql("SELECT 1 FROM sessions WHERE id = ? AND lane_id = ?").get(b.resumeSessionId, laneId)