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:
@@ -56,6 +56,7 @@ import type {
|
||||
CcMdItem,
|
||||
} from "../../lib/api";
|
||||
import type { Session, LaneTool } from "../../lib/types";
|
||||
import { LANE_TOOL_LABEL } from "../../lib/types";
|
||||
import { Select } from "../Select";
|
||||
|
||||
export interface SlashCommand {
|
||||
@@ -430,10 +431,13 @@ export function RunSetup(props: RunSetupProps) {
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Field label={t("fields.model")}>
|
||||
<ModelPicker value={props.model} onChange={props.onModelChange} tool={props.tool} />
|
||||
</Field>
|
||||
{props.tool === "codex" ? (
|
||||
{/* `agy` takes no model, permission or effort flag. */}
|
||||
{props.tool !== "antigravity" && (
|
||||
<Field label={t("fields.model")}>
|
||||
<ModelPicker value={props.model} onChange={props.onModelChange} tool={props.tool} />
|
||||
</Field>
|
||||
)}
|
||||
{props.tool === "antigravity" ? null : props.tool === "codex" ? (
|
||||
<>
|
||||
<Field label={t("fields.sandbox")}>
|
||||
<Select<CodexSandbox>
|
||||
@@ -472,17 +476,19 @@ export function RunSetup(props: RunSetupProps) {
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
<Field label={t("fields.effort")}>
|
||||
<Select<EffortLevel>
|
||||
value={props.effort}
|
||||
onChange={props.onEffortChange}
|
||||
options={RUN_EFFORT_CHOICES.map((c) => ({
|
||||
value: c.id,
|
||||
label: c.label,
|
||||
hint: c.hint,
|
||||
}))}
|
||||
/>
|
||||
</Field>
|
||||
{props.tool !== "antigravity" && (
|
||||
<Field label={t("fields.effort")}>
|
||||
<Select<EffortLevel>
|
||||
value={props.effort}
|
||||
onChange={props.onEffortChange}
|
||||
options={RUN_EFFORT_CHOICES.map((c) => ({
|
||||
value: c.id,
|
||||
label: c.label,
|
||||
hint: c.hint,
|
||||
}))}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center rounded-md border border-border bg-surface-2 p-0.5 text-[11.5px]">
|
||||
<Seg
|
||||
@@ -542,9 +548,9 @@ export function RunSetup(props: RunSetupProps) {
|
||||
value={props.prompt}
|
||||
onChange={props.onPromptChange}
|
||||
onSubmit={() => handleStart(props)}
|
||||
placeholder={t("fields.promptPlaceholderTerminal")}
|
||||
placeholder={t("fields.promptPlaceholderTerminal", { tool: LANE_TOOL_LABEL[props.tool] })}
|
||||
rows={5}
|
||||
slash={props.tool !== "codex"}
|
||||
slash={props.tool === "claude"}
|
||||
fileCwd={props.resumeSession?.cwd || props.cwd}
|
||||
/>
|
||||
<div className="mt-1 text-[10px] text-fg-muted">
|
||||
@@ -609,18 +615,18 @@ export function RunSetup(props: RunSetupProps) {
|
||||
function handleStart(props: RunSetupProps, session?: Session) {
|
||||
const resume = session ?? props.resumeSession;
|
||||
const codex = props.tool === "codex";
|
||||
const agy = props.tool === "antigravity";
|
||||
props.onStart({
|
||||
laneId: props.laneId,
|
||||
// A resume is pinned to its own session's folder — that's what the locked
|
||||
// cwd field shows, so it's what gets sent.
|
||||
cwd: resume?.cwd || props.cwd || undefined,
|
||||
model: props.model || undefined,
|
||||
permissionMode: codex ? undefined : props.permissionMode || undefined,
|
||||
model: agy ? undefined : props.model || undefined,
|
||||
permissionMode: codex || agy ? undefined : props.permissionMode || undefined,
|
||||
sandbox: codex ? props.sandbox : undefined,
|
||||
approval: codex ? props.approval : undefined,
|
||||
effort: props.effort || undefined,
|
||||
resumeSessionId: codex ? undefined : resume?.id || undefined,
|
||||
resumeLast: codex && !resume ? true : undefined,
|
||||
effort: agy ? undefined : props.effort || undefined,
|
||||
resumeSessionId: codex || agy ? undefined : resume?.id || undefined,
|
||||
// A resume continues a session that already has its own history — sending
|
||||
// whatever is sitting in the prompt box would fire an extra turn nobody
|
||||
// asked for, so a resume carries no prompt at all.
|
||||
|
||||
Reference in New Issue
Block a user