feat(lanes): Antigravity lanes, release exited panes, fresh Codex starts
- antigravity: a third lane tool running Antigravity's `agy` CLI. `-i`
carries the first prompt, `--continue` resumes; `agy` has no model,
effort or permission flag, and `--dangerously-skip-permissions` is never
emitted. No pipeline, no agents install, and no AI Router (native only,
else EUNSUPPORTED). Add lane gains an Antigravity choice and the binary
probe maps it to `agy`.
- UI: the lane card no longer renders a frame holding only a "Codex" chip,
and the prompt box names the lane's CLI ("Ask Codex anything…"), via
LANE_TOOL_LABEL.
- runs: a pane that exits is released on a 10 s server beat
(healAllRunIds, DASHBOARD_RUN_HEAL_MS). Codex and Antigravity fire no
hook, so their lanes stayed "running" until something re-read them.
- codex: Start no longer sends resumeLast — only "Resume last session"
continues the previous thread.
This commit is contained in:
+13
-1
@@ -106,6 +106,16 @@ function healRunId(lane) {
|
||||
return lanesLib.getLane(lane.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Heal every lane at once. A claude pane that exits fires a hook, which
|
||||
* broadcasts the lane and so re-reads it; a codex or antigravity pane fires
|
||||
* nothing, so without this sweep its lane read "running" until something
|
||||
* else happened to fetch it.
|
||||
*/
|
||||
function healAllRunIds() {
|
||||
for (const lane of lanesLib.listLanes()) if (lane.run_id) healRunId(lane);
|
||||
}
|
||||
|
||||
function payload(lane) {
|
||||
const healed = healRunId(lane);
|
||||
return lanesLib.lanePayload(healed, lastEventAge(healed));
|
||||
@@ -697,7 +707,8 @@ router.post("/:id/router/apply", sameOriginGuard, (req, res) => {
|
||||
broadcastLane(lane.id);
|
||||
res.json({ ...result, restart_required: live, run_id: live ? runId : null });
|
||||
} catch (err) {
|
||||
const status = { ENOTFOUND: 404, EBADJSON: 409, EBADNAME: 400 }[err.code] || 500;
|
||||
const status =
|
||||
{ ENOTFOUND: 404, EBADJSON: 409, EBADNAME: 400, EUNSUPPORTED: 400 }[err.code] || 500;
|
||||
res.status(status).json({ error: { code: err.code || "ERUNTIME", message: err.message } });
|
||||
}
|
||||
});
|
||||
@@ -1777,3 +1788,4 @@ router.delete("/:id", sameOriginGuard, (req, res) => {
|
||||
|
||||
module.exports = router;
|
||||
module.exports.broadcastLane = broadcastLane;
|
||||
module.exports.healAllRunIds = healAllRunIds;
|
||||
|
||||
@@ -290,12 +290,13 @@ router.get("/binary", (req, res) => {
|
||||
// taken from a fixed allowlist, never from the query string directly — this
|
||||
// string reaches `which`.
|
||||
const tool = req.query.tool === undefined ? "claude" : String(req.query.tool);
|
||||
if (tool !== "claude" && tool !== "codex") {
|
||||
const bins = { claude: "claude", codex: "codex", antigravity: "agy" };
|
||||
if (!Object.hasOwn(bins, tool)) {
|
||||
return res.status(400).json({ error: { code: "EBADTOOL", message: "unknown tool" } });
|
||||
}
|
||||
const which = require("node:child_process").spawnSync(
|
||||
process.platform === "win32" ? "where" : "which",
|
||||
[tool],
|
||||
[bins[tool]],
|
||||
{ encoding: "utf8" }
|
||||
);
|
||||
const stdout = (which.stdout || "").trim();
|
||||
|
||||
Reference in New Issue
Block a user