fix(pet): a member's pet chat starts in <their folder>/sagi
POST /api/lanes/ensure without a cwd defaulted to the dashboard's own home, outside a member's folder, so every member's pet chat failed with "a workspace must be inside your own folder" (EFOLDER). A member now gets <their folder>/sagi, made as them (write-as mkdir) on first use.
This commit is contained in:
@@ -192,3 +192,31 @@ describe("a member's new project folder", () => {
|
||||
assert.equal(fs.existsSync(path.join(TMP, "host-new")), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("the pet chat's workspace for a member", () => {
|
||||
it("is <their folder>/sagi, made as them — never the host's home (EFOLDER)", async () => {
|
||||
const { fakeSudo } = require("./fixtures/fake-sudo");
|
||||
const sudo = fakeSudo();
|
||||
runAs.__setExecImpl(sudo.exec);
|
||||
const login = await fetch(`${BASE}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username: "mia", password: "member-pass-1" }),
|
||||
});
|
||||
const cm = login.headers.get("set-cookie").split(";")[0];
|
||||
const ensure = () =>
|
||||
fetch(`${BASE}/api/lanes/ensure`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", cookie: cm },
|
||||
body: JSON.stringify({ title: "Sagi" }),
|
||||
}).then(async (r) => ({ status: r.status, body: await r.json() }));
|
||||
const first = await ensure();
|
||||
assert.equal(first.status, 201, JSON.stringify(first.body));
|
||||
const want = fs.realpathSync(path.join(USERS, "mia", "sagi"));
|
||||
assert.equal(first.body.lane.cwd, want);
|
||||
const mk = sudo.calls.find((c) => c.includes("/opt/agenthub/libexec/write-as"));
|
||||
assert.ok(mk && mk.includes("ah-mia") && mk.includes("mkdir"));
|
||||
const again = await ensure();
|
||||
assert.equal(again.body.lane.id, first.body.lane.id, "the same workspace next time");
|
||||
});
|
||||
});
|
||||
|
||||
+34
-3
@@ -436,6 +436,23 @@ function refuseFolder(res) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A member's folder for a workspace no caller picked a directory for (the
|
||||
* pet chat): `<own folder>/sagi`, made AS the member under S3 (`mkdirAs`,
|
||||
* never a host write in their home). The caller still runs it through
|
||||
* `folderAllowed`, so a link planted there cannot lead outside.
|
||||
*/
|
||||
function ownDefaultFolder(own) {
|
||||
const target = path.join(own, "sagi");
|
||||
if (!fs.existsSync(target)) {
|
||||
const runAs = require("../lib/run-as");
|
||||
const acct = runAs.accountForPath(target);
|
||||
if (acct) runAs.mkdirAs(acct, target);
|
||||
else fs.mkdirSync(target, { recursive: true });
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Idempotent "which lane owns this directory?" — the Workspace page opens on a
|
||||
* cwd, not on a lane id, so it needs one lane to exist for that cwd without
|
||||
@@ -447,10 +464,24 @@ router.post("/ensure", sameOriginGuard, (req, res) => {
|
||||
// specific directory (e.g. Sagi's mascot companion) get a real, existing
|
||||
// path and lane uniqueness stays meaningful. Callers that do care pass one
|
||||
// through unchanged.
|
||||
// A member's default is NOT the host's home (outside their folder: EFOLDER,
|
||||
// the pet chat could never start) nor their own home (no workspace under
|
||||
// S3), but `<their folder>/sagi`, made as them on first use.
|
||||
// Canonical before the lookup, so the lookup and createLane's insert agree.
|
||||
const cwd = lanesLib.canonicalCwd(
|
||||
typeof body.cwd === "string" && body.cwd.trim() ? body.cwd.trim() : os.homedir()
|
||||
);
|
||||
let cwd;
|
||||
if (typeof body.cwd === "string" && body.cwd.trim()) {
|
||||
cwd = lanesLib.canonicalCwd(body.cwd.trim());
|
||||
} else if (scope.ownFolder(req)) {
|
||||
try {
|
||||
cwd = lanesLib.canonicalCwd(ownDefaultFolder(scope.ownFolder(req)));
|
||||
} catch (err) {
|
||||
return res.status(err.code === "EMEMBERGONE" ? 409 : 500).json({
|
||||
error: { code: err.code || "EMKDIR", message: err.message },
|
||||
});
|
||||
}
|
||||
} else {
|
||||
cwd = lanesLib.canonicalCwd(os.homedir());
|
||||
}
|
||||
|
||||
if (body.tool !== undefined && !aiTools.has(body.tool)) {
|
||||
return res.status(400).json({ error: { code: "EBADTOOL", message: "unknown tool" } });
|
||||
|
||||
Reference in New Issue
Block a user