Run tasks on Codex beside Claude; drop turn caps and mid-turn snapshots

This commit is contained in:
Ian Gulliver
2026-10-15 14:55:58 -07:00
parent 17898b0191
commit 74707f6398
53 changed files with 1904 additions and 1295 deletions
+27 -13
View File
@@ -30,7 +30,19 @@ export type Head = {
dispose?: () => void;
};
export const models = ["claude-opus-5-5", "claude-sonnet-5-5", "claude-haiku-5-5", "claude-fable-5-1"];
export const models = [
"claude-opus-5-5",
"claude-sonnet-5-5",
"claude-haiku-5-5",
"claude-fable-5-1",
"gpt-6.1-sol",
"gpt-6-astra",
"gpt-6-sol",
"gpt-6-luna",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
];
const quietReply = "NO_REPLY";
@@ -134,7 +146,7 @@ function taskHead(rerender: () => void): Head {
return [h("div", { class: "head-actions" }, task.status)];
}
const target = state.projects.find((p) => p.id === task.project_id)?.default_branch ?? "the default branch";
const busy = sess?.state === "running" || sess?.state === "queued";
const busy = sess?.state === "running";
const held = task.merge_queued_at !== undefined;
const merge = h(
"button",
@@ -308,7 +320,7 @@ export function mountChat(root: HTMLElement, src: ChatSource, makeHead: (rerende
const sess = src.session();
const live = sess && sess.state !== "completed" ? sess : undefined;
composerModel = live ? live.model || context.get(live.id)?.model || "" : "";
const form = composeForm(src, key, live?.id, live?.state ?? "idle", composerModel, showPending);
const form = composeForm(src, key, live?.id, live?.state ?? "idle", composerModel, live?.vendor, showPending);
const old = composer.querySelector("textarea");
const caret = old !== null && document.activeElement === old ? [old.selectionStart, old.selectionEnd] : undefined;
replace(composer, form);
@@ -685,10 +697,15 @@ function releaseImages(imgs: PendingImage[]): void {
const modelChoices = new Map<string, string>();
function modelSelect(key: string, current: string): HTMLSelectElement {
const options = current && !models.includes(current) ? [current, ...models] : models;
function vendorOf(model: string): string {
return model.startsWith("gpt-") ? "codex" : "claude";
}
function modelSelect(key: string, current: string, vendor: string | undefined): HTMLSelectElement {
const offered = vendor ? models.filter((m) => vendorOf(m) === vendor) : models;
const options = current && !offered.includes(current) ? [current, ...offered] : offered;
const select = h("select", { title: "Model for the next turn" }, ...options.map((m) => h("option", { value: m }, m)));
select.value = modelChoices.get(key) ?? (current || models[0]!);
select.value = modelChoices.get(key) ?? (current || options[0]!);
select.onchange = () => {
modelChoices.set(key, select.value);
};
@@ -701,16 +718,13 @@ function composeForm(
sessionId: string | undefined,
sessionState: string,
currentModel: string,
vendor: string | undefined,
showPending: (text: string, imgs: PendingImage[]) => () => void,
): HTMLElement {
const text = h("textarea", { placeholder: src.placeholder(sessionState), rows: 3 });
const clearDraft = keepDraft(text, `message:${key}`);
const send = h("button", { type: "submit", disabled: !src.canSend(sessionState) }, "Send");
const interrupt = h(
"button",
{ type: "button", class: "danger", disabled: sessionState !== "running" && sessionState !== "queued" },
sessionState === "queued" ? "Cancel queued turn" : "Interrupt",
);
const interrupt = h("button", { type: "button", class: "danger", disabled: sessionState !== "running" }, "Interrupt");
interrupt.onclick = async () => {
if (!sessionId) {
return;
@@ -723,7 +737,7 @@ function composeForm(
};
const modelKey = `message:${sessionId ?? key}`;
const model = modelSelect(modelKey, currentModel);
const model = modelSelect(modelKey, currentModel, vendor);
const form = h("form", { class: "compose" });
const tray = imageTray(form, text, `message:${key}`);
form.append(tray.el, text, h("div", { class: "compose-actions" }, model, interrupt, send));
@@ -1064,7 +1078,7 @@ function messageImages(ids: unknown): HTMLElement | null {
export async function confirmMerge(taskId: string): Promise<void> {
const task = findTask(taskId);
const sessionState = currentSession(taskId)?.state;
if (!task || task.status !== "active" || task.merge_queued_at !== undefined || sessionState === "running" || sessionState === "queued") {
if (!task || task.status !== "active" || task.merge_queued_at !== undefined || sessionState === "running") {
return;
}
const target = state.projects.find((p) => p.id === task.project_id)?.default_branch ?? "the default branch";