feat(sagi): spawn a real claude session for Sagi's Ask handoff

Unmatched Ask questions now adopt a dedicated "Sagi" lane (POST
/api/lanes/ensure, cwd defaults to $HOME) and spawn a tmux-backed claude
run there instead of just prefilling the Run page composer.
This commit is contained in:
2026-08-24 09:39:28 +07:00
parent 510f015fdf
commit b1bca07c47
6 changed files with 99 additions and 26 deletions
+18 -4
View File
@@ -161,11 +161,25 @@ router.get("/branches", async (req, res) => {
*/
router.post("/ensure", sameOriginGuard, (req, res) => {
const body = req.body || {};
const owner = lanesLib.resolveLaneByCwd(body.cwd);
if (owner) return res.json({ lane: payload(owner), created: false });
// Default the cwd to the user's home so callers that don't care about a
// 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.
const cwd = typeof body.cwd === "string" && body.cwd.trim() ? body.cwd.trim() : os.homedir();
const owner = lanesLib.resolveLaneByCwd(cwd);
if (owner) {
// First-write wins on the title: an ensure-with-title on an existing lane
// is how callers like Sagi adopt the lane, so don't clobber a real title
// with an empty one.
if (body.title && !owner.title) {
const updated = lanesLib.updateLane(owner.id, { title: body.title });
return res.json({ lane: payload(updated || owner), created: false });
}
return res.json({ lane: payload(owner), created: false });
}
try {
const lane = lanesLib.createLane({
cwd: body.cwd,
cwd,
title: body.title || "",
...(body.pipeline ? { pipeline: body.pipeline } : {}),
});
@@ -178,7 +192,7 @@ router.post("/ensure", sameOriginGuard, (req, res) => {
// The cwd UNIQUE constraint is the arbiter: someone else won the race, so
// re-read and return THEIR lane rather than reporting a conflict.
if (err.code === "SQLITE_CONSTRAINT_UNIQUE" || String(err.message).includes("UNIQUE")) {
const winner = lanesLib.resolveLaneByCwd(body.cwd);
const winner = lanesLib.resolveLaneByCwd(cwd);
if (winner) return res.json({ lane: payload(winner), created: false });
}
return res.status(500).json({ error: { code: err.code, message: err.message } });