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:
@@ -116,15 +116,17 @@ describe("installAgents", () => {
|
||||
assert.ok(fs.existsSync(path.join(cwd, ".claude", "agents")));
|
||||
});
|
||||
|
||||
it("installs nothing for a codex lane — no .claude/agents/, no AGENTS.md", async () => {
|
||||
const cwd = makeRepoDir();
|
||||
const lane = lanesLib.createLane({ title: "cx", cwd, kind: "managed", tool: "codex" });
|
||||
for (const tool of ["codex", "antigravity"]) {
|
||||
it(`installs nothing for a ${tool} lane — no .claude/agents/, no AGENTS.md`, async () => {
|
||||
const cwd = makeRepoDir();
|
||||
const lane = lanesLib.createLane({ title: "cx", cwd, kind: "managed", tool });
|
||||
|
||||
const result = await installAgents(lane);
|
||||
assert.deepEqual(result.installed, []);
|
||||
assert.equal(fs.existsSync(path.join(cwd, ".claude", "agents")), false);
|
||||
assert.equal(fs.existsSync(path.join(cwd, "AGENTS.md")), false);
|
||||
});
|
||||
const result = await installAgents(lane);
|
||||
assert.deepEqual(result.installed, []);
|
||||
assert.equal(fs.existsSync(path.join(cwd, ".claude", "agents")), false);
|
||||
assert.equal(fs.existsSync(path.join(cwd, "AGENTS.md")), false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("codex pipeline cleanup", () => {
|
||||
|
||||
@@ -1302,4 +1302,20 @@ describe("lane ensure, start mode, lane_id and releasing a finished run", () =>
|
||||
restorePath();
|
||||
}
|
||||
});
|
||||
|
||||
it("healAllRunIds releases a lane whose pane exited, with no read to trigger it", async () => {
|
||||
// A codex/antigravity pane fires no hook on exit, so nothing re-reads its
|
||||
// lane; the server's periodic sweep is what has to notice.
|
||||
const lanesLib = require("../lib/lanes");
|
||||
const { healAllRunIds } = require("../routes/lanes");
|
||||
const lane = await adoptedLane("heal-all-sweep");
|
||||
// No tmux session by this name exists on the suite's private server.
|
||||
lanesLib.updateLane(lane.id, { run_id: `agenthub-lane-${lane.id}`, status: "running" });
|
||||
|
||||
healAllRunIds();
|
||||
|
||||
const after = lanesLib.getLane(lane.id);
|
||||
assert.equal(after.run_id, null);
|
||||
assert.equal(after.status, "idle");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -398,6 +398,27 @@ describe("pty-run", () => {
|
||||
assert.ok(!call.some((a) => String(a).startsWith("--dangerously-bypass")));
|
||||
});
|
||||
|
||||
it("spawnRun runs `agy` for an antigravity lane, dropping flags it has no use for", () => {
|
||||
const call = captureNewSession(() =>
|
||||
pty.spawnRun({
|
||||
laneId: 13,
|
||||
cwd: "/tmp/repo",
|
||||
tool: "antigravity",
|
||||
model: "opus",
|
||||
effort: "high",
|
||||
permissionMode: "bypassPermissions",
|
||||
initialPrompt: "hello",
|
||||
})
|
||||
);
|
||||
assert.deepEqual(paneArgv(call), ["agy", "-i", "hello"]);
|
||||
|
||||
tmux.__reset();
|
||||
const last = captureNewSession(() =>
|
||||
pty.spawnRun({ laneId: 14, cwd: "/tmp/repo", tool: "antigravity", resumeLast: true })
|
||||
);
|
||||
assert.deepEqual(paneArgv(last), ["agy", "--continue"]);
|
||||
});
|
||||
|
||||
// `dashboard_runs.mode` is NOT NULL and recordRun swallows insert errors, so a
|
||||
// null there persisted nothing at all while every call still looked fine: the
|
||||
// runs modal rendered a row with no cwd, model, prompt or start time, and a
|
||||
|
||||
@@ -233,6 +233,24 @@ describe("router-apply — the written file must survive a lane reset", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("router-apply — Antigravity lane", () => {
|
||||
const agyLane = () => ({ id: 9, cwd: LANE_CWD, tool: "antigravity" });
|
||||
|
||||
it("accepts native and writes nothing", () => {
|
||||
assert.equal(applyRouterToLane(agyLane(), "native").file, null);
|
||||
assert.equal(fs.existsSync(SETTINGS), false);
|
||||
});
|
||||
|
||||
it("refuses a real router instead of writing Claude's settings file", () => {
|
||||
const { name } = saveNineRouter();
|
||||
assert.throws(
|
||||
() => applyRouterToLane(agyLane(), name),
|
||||
(err) => err.code === "EUNSUPPORTED"
|
||||
);
|
||||
assert.equal(fs.existsSync(SETTINGS), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("router-apply — Codex lane", () => {
|
||||
it("writes a profile layer file, never the base config", () => {
|
||||
const base = path.join(process.env.CODEX_HOME, "config.toml");
|
||||
|
||||
Reference in New Issue
Block a user