feat(api): accept a lane tool on create, start and binary probe

This commit is contained in:
2026-08-27 11:00:13 +07:00
parent e8070f04d1
commit ce830094f0
4 changed files with 65 additions and 10 deletions
+20 -1
View File
@@ -260,6 +260,11 @@ router.post("/ensure", sameOriginGuard, (req, res) => {
// 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();
if (body.tool !== undefined && !lanesLib.VALID_TOOLS.has(body.tool)) {
return res.status(400).json({ error: { code: "EBADTOOL", message: "unknown tool" } });
}
const owner = lanesLib.resolveLaneByCwd(cwd);
if (owner) {
// First-write wins on the title: an ensure-with-title on an existing lane
@@ -276,11 +281,12 @@ router.post("/ensure", sameOriginGuard, (req, res) => {
cwd,
title: body.title || "",
...(body.pipeline ? { pipeline: body.pipeline } : {}),
...(body.tool ? { tool: body.tool } : {}),
});
broadcastLane(lane.id);
return res.status(201).json({ lane: payload(lane), created: true });
} catch (err) {
if (err.code === "EBADCWD" || err.code === "EBADPIPELINE") {
if (err.code === "EBADCWD" || err.code === "EBADPIPELINE" || err.code === "EBADTOOL") {
return res.status(400).json({ error: { code: err.code, message: err.message } });
}
// The cwd UNIQUE constraint is the arbiter: someone else won the race, so
@@ -641,6 +647,11 @@ router.get("/:id/ci/status", async (req, res) => {
*/
router.post("/worktree", sameOriginGuard, async (req, res) => {
const body = req.body || {};
if (body.tool !== undefined && !lanesLib.VALID_TOOLS.has(body.tool)) {
return res.status(400).json({ error: { code: "EBADTOOL", message: "unknown tool" } });
}
const sourceRepo = body.sourceRepo;
if (
typeof sourceRepo !== "string" ||
@@ -706,6 +717,7 @@ router.post("/worktree", sameOriginGuard, async (req, res) => {
base_branch: body.base || null,
slug,
...(body.pipeline ? { pipeline: body.pipeline } : {}),
...(body.tool ? { tool: body.tool } : {}),
});
lane = lanesLib.updateLane(lane.id, { status: "provisioning" });
} catch (err) {
@@ -1230,9 +1242,16 @@ router.post("/:id/:action", sameOriginGuard, async (req, res) => {
laneId: current.id,
initialPrompt: body.prompt || "",
cwd: current.cwd,
// The tool is the LANE's fact, never the request's: a client that
// asked for the other CLI would otherwise get a pane that doesn't
// match the lane's badge, its history, or its binary probe.
tool: current.tool || "claude",
model: body.model,
permissionMode: body.permissionMode,
effort: body.effort,
sandbox: body.sandbox,
approval: body.approval,
resumeLast: body.resumeLast === true,
resumeSessionId: body.resumeSessionId || (body.resume ? current.session_id : undefined),
});
lanesLib.updateLane(current.id, { run_id: handle.id, status: "running" });