Something went wrong. Try again.
presents omp as codex for use with chatgpt remote
Something went wrong. Try again.
72 kB · 1510 lines
JavaScript
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511import test from "node:test";import assert from "node:assert/strict";import fs from "node:fs/promises";import path from "node:path";import { makeHarness } from "./helpers.mjs";import { MockBackendFactory } from "../src/backend/mock.mjs";import { StateStore } from "../src/store.mjs";import { cwdToSessionDirName } from "../src/omp-sessions.mjs";
async function withHarness(options, fn) { const harness = await makeHarness(options); try { await fn(harness); } finally { await harness.close(); }}
test("subscribed native OMP threads broadcast external activity changes", () => withHarness({}, async (h) => { await h.initialize(); const sessionDir = path.join(h.temp, "sessions", cwdToSessionDirName(h.temp)); const sessionFile = path.join(sessionDir, "2026-08-24T00-00-00-000Z_native-watched.jsonl"); await fs.mkdir(sessionDir, { recursive: true }); await fs.writeFile(sessionFile, [ JSON.stringify({ type: "session", id: "native-watched", timestamp: new Date().toISOString(), cwd: h.temp }), JSON.stringify({ type: "message", id: "user-first", timestamp: new Date().toISOString(), message: { role: "user", content: [{ type: "text", text: "first" }] } }), JSON.stringify({ type: "message", id: "assistant-first", timestamp: new Date().toISOString(), message: { role: "assistant", stopReason: "stop", content: [{ type: "text", text: "done" }] } }), ].join("\n") + "\n");
const read = await h.request("thread/read", { threadId: "native-watched" }); assert.equal(read.thread.status.type, "idle"); await fs.appendFile(sessionFile, JSON.stringify({ type: "message", id: "user-second", timestamp: new Date().toISOString(), message: { role: "user", content: [{ type: "text", text: "second" }] }, }) + "\n"); const active = await h.waitFor((message) => message.method === "thread/status/changed" && message.params?.threadId === "native-watched" && message.params?.status?.type === "active" ); assert.deepEqual(active.params.status, { type: "active", activeFlags: [] });
await fs.appendFile(sessionFile, JSON.stringify({ type: "message", id: "assistant-second", timestamp: new Date().toISOString(), message: { role: "assistant", stopReason: "stop", content: [{ type: "text", text: "done again" }] }, }) + "\n"); const idle = await h.waitFor((message) => message.method === "thread/status/changed" && message.params?.threadId === "native-watched" && message.params?.status?.type === "idle" ); assert.deepEqual(idle.params.status, { type: "idle" });}));
test("externally started OMP turns stream items and turn lifecycle to subscribers", () => withHarness({}, async (h) => { await h.initialize(); const sessionDir = path.join(h.temp, "sessions", cwdToSessionDirName(h.temp)); const sessionFile = path.join(sessionDir, "2026-08-24T00-00-00-000Z_native-streamed.jsonl"); await fs.mkdir(sessionDir, { recursive: true }); await fs.writeFile(sessionFile, [ JSON.stringify({ type: "session", id: "native-streamed", timestamp: new Date().toISOString(), cwd: h.temp }), JSON.stringify({ type: "message", id: "user-1", timestamp: new Date().toISOString(), message: { role: "user", content: [{ type: "text", text: "first" }] } }), JSON.stringify({ type: "message", id: "assistant-1", timestamp: new Date().toISOString(), message: { role: "assistant", stopReason: "stop", content: [{ type: "text", text: "first answer" }] } }), ].join("\n") + "\n");
const read = await h.request("thread/read", { threadId: "native-streamed" }); assert.equal(read.thread.turns.length, 1); const historyReplayed = h.messages.some((message) => message.method === "turn/started"); assert.equal(historyReplayed, false, "existing history must not be replayed as new turns");
await fs.appendFile(sessionFile, JSON.stringify({ type: "message", id: "user-2", timestamp: new Date().toISOString(), message: { role: "user", content: [{ type: "text", text: "second" }] }, }) + "\n"); const started = await h.waitFor((message) => message.method === "turn/started" && message.params?.threadId === "native-streamed" ); assert.ok(started.params.turn.id);
await fs.appendFile(sessionFile, JSON.stringify({ type: "message", id: "assistant-2", timestamp: new Date().toISOString(), message: { role: "assistant", stopReason: "stop", content: [{ type: "text", text: "second answer" }] }, }) + "\n");
const itemEvent = await h.waitFor((message) => (message.method === "item/started" || message.method === "item/completed" || message.method === "item/updated") && message.params?.threadId === "native-streamed" && message.params?.item?.type === "agentMessage" ); assert.match(JSON.stringify(itemEvent.params.item), /second answer/);
const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params?.threadId === "native-streamed" && message.params?.turn?.id === started.params.turn.id ); assert.equal(completed.params.turn.status, "completed");}));
test("externally projected commands complete once with durable output", () => withHarness({}, async (h) => { await h.initialize(); const sessionDir = path.join(h.temp, "sessions", cwdToSessionDirName(h.temp)); const sessionFile = path.join(sessionDir, "2026-08-24T00-00-00-000Z_native-command.jsonl"); await fs.mkdir(sessionDir, { recursive: true }); await fs.writeFile(sessionFile, [ JSON.stringify({ type: "session", id: "native-command", timestamp: new Date().toISOString(), cwd: h.temp }), JSON.stringify({ type: "message", id: "user-first", timestamp: new Date().toISOString(), message: { role: "user", content: [{ type: "text", text: "first" }] } }), JSON.stringify({ type: "message", id: "assistant-first", timestamp: new Date().toISOString(), message: { role: "assistant", stopReason: "stop", content: [{ type: "text", text: "done" }] } }), ].join("\n") + "\n");
await h.request("thread/read", { threadId: "native-command" }); await fs.appendFile(sessionFile, JSON.stringify({ type: "message", id: "user-second", timestamp: new Date().toISOString(), message: { role: "user", content: [{ type: "text", text: "check jobs" }] }, }) + "\n"); const turnStarted = await h.waitFor((message) => message.method === "turn/started" && message.params?.threadId === "native-command" );
await fs.appendFile(sessionFile, JSON.stringify({ type: "message", id: "assistant-command", timestamp: new Date().toISOString(), message: { role: "assistant", content: [{ type: "toolCall", id: "command-call", name: "bash", arguments: { command: "check-spindle-jobs" }, }], }, }) + "\n"); const itemStarted = await h.waitFor((message) => message.method === "item/started" && message.params?.threadId === "native-command" && message.params?.item?.id === "command-call" ); assert.equal(itemStarted.params.item.status, "inProgress"); assert.equal(itemStarted.params.item.aggregatedOutput, ""); assert.equal(itemStarted.params.item.durationMs, null);
const output = "running=3 queued=0\nWall time: 18.14 seconds"; await fs.appendFile(sessionFile, JSON.stringify({ type: "message", id: "command-result", timestamp: new Date().toISOString(), message: { role: "toolResult", toolCallId: "command-call", toolName: "bash", content: [{ type: "text", text: output }], details: { wallTimeMs: 18140 }, isError: false, }, }) + "\n"); const itemCompleted = await h.waitFor((message) => message.method === "item/completed" && message.params?.threadId === "native-command" && message.params?.item?.id === "command-call" ); assert.equal(itemCompleted.params.item.status, "completed"); assert.equal(itemCompleted.params.item.aggregatedOutput, output); assert.equal(itemCompleted.params.item.durationMs, 18140);
await fs.appendFile(sessionFile, JSON.stringify({ type: "message", id: "assistant-final", timestamp: new Date().toISOString(), message: { role: "assistant", stopReason: "stop", content: [{ type: "text", text: "still running" }] }, }) + "\n"); await h.waitFor((message) => message.method === "turn/completed" && message.params?.threadId === "native-command" && message.params?.turn?.id === turnStarted.params.turn.id );
const lifecycle = h.messages.filter((message) => (message.method === "item/started" || message.method === "item/completed") && message.params?.threadId === "native-command" && message.params?.item?.id === "command-call" ); assert.deepEqual(lifecycle.map((message) => message.method), ["item/started", "item/completed"]);}));
test("reordered item projections do not republish an unchanged external item", () => withHarness({}, async (h) => { await h.initialize(); const threadId = "native-reordered"; const item = { id: "item-1", type: "agentMessage", status: "completed", text: "stable answer", createdAt: 1787460346, }; const turn = (projectedItem) => ({ id: "turn-1", status: "completed", items: [projectedItem], tokenUsage: null, }); // Same content, different key insertion order: exactly what the local and // reparsed projections produce for one item. const reordered = Object.fromEntries(Object.entries(item).reverse()); assert.notEqual(JSON.stringify(item), JSON.stringify(reordered), "fixture must differ only in key order");
let projected = item; h.server.store.getThread = async (id) => (id === threadId ? { id: threadId, turns: [turn(projected)], model: null } : null); h.server.store.getExternalThreadStatus = async () => ({ type: "active", activeFlags: [] });
// thread/read subscribes and seeds the projection from the first ordering. await h.request("thread/read", { threadId });
projected = reordered; // Two full poll intervals: the projection now only differs in key order. await new Promise((resolve) => setTimeout(resolve, 2_500));
const republished = h.messages.filter((message) => (message.method === "item/started" || message.method === "item/updated" || message.method === "item/completed") && message.params?.threadId === threadId ); assert.deepEqual(republished, [], "key-order-only changes must not republish an item");}));
test("failed session setup releases the turn-start reservation without persisting a ghost turn", () => { const base = new MockBackendFactory(); let attempts = 0; const backendFactory = { listModels: (...args) => base.listModels(...args), async openSession(options) { attempts += 1; if (attempts === 1) throw new Error("synthetic open failure"); return base.openSession(options); }, }; return withHarness({ backendFactory }, async (h) => { await h.initialize(); const thread = await h.store.createThread({ cwd: h.temp }); await assert.rejects( h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "first" }] }), /synthetic open failure/ ); const afterFailure = await h.request("thread/read", { threadId: thread.id }); assert.equal(afterFailure.thread.status.type, "idle"); assert.equal(afterFailure.thread.turns.length, 0);
const retry = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "retry" }] }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params?.turn?.id === retry.turn.id ); assert.equal(completed.params.turn.status, "completed"); });});
test("initialization and model discovery", () => withHarness({}, async (h) => { const initialized = await h.initialize(); assert.match(initialized.userAgent, /omp-codex-bridge/); const models = await h.request("model/list", {}); assert.equal(models.data[0].id, "mock/omp"); assert.deepEqual(await h.request("plugin/list", {}), { marketplaces: [], featuredPluginIds: [], marketplaceLoadErrors: [], }); assert.deepEqual(await h.request("plugin/installed", {}), { marketplaces: [], featuredPluginIds: [], marketplaceLoadErrors: [], }); assert.deepEqual(await h.request("remoteControl/enable", { enabled: true }), {});}));
test("config/read keeps backend routing tags out of iOS new-thread picker state", () => { const backendFactory = new MockBackendFactory(); backendFactory.modelProviderId = "omp-codex-bridge"; return withHarness({ backendFactory, config: { defaultModel: "gpt-5.6-luna-via-prime-agent" }, }, async (h) => { await h.request("initialize", { clientInfo: { name: "codex_chatgpt_ios_remote", version: "1" }, capabilities: {}, }); await h.notify("initialized"); const { config } = await h.request("config/read", { cwd: h.temp }); assert.equal(config.model, "gpt-5.6-luna"); assert.equal(config.model_provider, "omp-codex-bridge"); });});
test("new-thread model picker writes update the defaults used by thread/start", () => withHarness({ config: { defaultModel: "mock/omp", defaultReasoningEffort: "medium" },}, async (h) => { await h.initialize(); const result = await h.request("config/batchWrite", { edits: [ { keyPath: "model", value: "claude-opus-5", mergeStrategy: "upsert" }, { keyPath: "model_reasoning_effort", value: "xhigh", mergeStrategy: "upsert" }, ], filePath: null, expectedVersion: null, reloadUserConfig: true, }); assert.equal(result.status, "ok");
const { config } = await h.request("config/read", { cwd: h.temp }); assert.equal(config.model, "claude-opus-5"); assert.equal(config.model_reasoning_effort, "xhigh");
const { thread } = await h.request("thread/start", { cwd: h.temp }); assert.equal(thread.model, "claude-opus-5"); assert.equal(thread.reasoningEffort, "xhigh");
const persisted = JSON.parse(await fs.readFile(path.join(h.temp, "config.json"), "utf8")); assert.equal(persisted.defaultModel, "claude-opus-5"); assert.equal(persisted.defaultReasoningEffort, "xhigh");}));
test("fuzzy file search supports single-shot and streaming mention completion", () => withHarness({}, async (h) => { await h.initialize(); await fs.mkdir(path.join(h.temp, "src"), { recursive: true }); await fs.mkdir(path.join(h.temp, "test"), { recursive: true }); await fs.mkdir(path.join(h.temp, "node_modules", "ignored"), { recursive: true }); await fs.writeFile(path.join(h.temp, "src", "app-server.mjs"), ""); await fs.writeFile(path.join(h.temp, "test", "app-server.test.mjs"), ""); await fs.writeFile(path.join(h.temp, "node_modules", "ignored", "app-server-copy.mjs"), "");
const single = await h.request("fuzzyFileSearch", { query: "apptest", roots: [h.temp], cancellationToken: "mention-1", }); assert.ok(single.files.some((file) => file.path === "test/app-server.test.mjs")); assert.equal(single.files.some((file) => file.path.includes("node_modules")), false); assert.ok(single.files.every((file) => file.root && file.file_name && ["file", "directory"].includes(file.match_type) && Number.isFinite(file.score) ));
assert.deepEqual(await h.request("fuzzyFileSearch/sessionStart", { sessionId: "mention-session", roots: [h.temp], }), {}); assert.deepEqual(await h.request("fuzzyFileSearch/sessionUpdate", { sessionId: "mention-session", query: "appserver", }), {}); const updated = await h.waitFor((message) => message.method === "fuzzyFileSearch/sessionUpdated" && message.params?.sessionId === "mention-session" ); assert.equal(updated.params.query, "appserver"); assert.ok(updated.params.files.some((file) => file.path === "src/app-server.mjs")); await h.waitFor((message) => message.method === "fuzzyFileSearch/sessionCompleted" && message.params?.sessionId === "mention-session" ); assert.deepEqual(await h.request("fuzzyFileSearch/sessionStop", { sessionId: "mention-session", }), {});}));
test("model discovery keeps the dynamic catalog visible and prioritizes requested models", () => { const openedSessions = []; const turnModels = []; const sessions = new MockBackendFactory(); const backendFactory = { async listModels() { return [ { id: "gpt-5.6-luna", model: "gpt-5.6-luna", displayName: "GPT-5.6-Luna" }, { id: "google-antigravity/gemini-3.7-flash", model: "google-antigravity/gemini-3.7-flash", displayName: "Gemini 3.7 Flash (Google)", hidden: true }, { id: "gemini-3.7-flash", model: "gemini-3.7-flash", displayName: "Gemini 3.7 Flash" }, { id: "anthropic/claude-opus-5", model: "anthropic/claude-opus-5", displayName: "Claude Opus 5 (Anthropic)", hidden: true }, { id: "claude-opus-5", model: "claude-opus-5", displayName: "Claude Opus 5" }, { id: "gpt-5.6-sol", model: "gpt-5.6-sol", displayName: "GPT-5.6-Sol" }, { id: "MercuriusDream/Nanbeige4.2-3B-mlx-8bit", model: "MercuriusDream/Nanbeige4.2-3B-mlx-8bit", displayName: "Nanbeige 4.2" }, { id: "anthropic/shared-model", model: "anthropic/shared-model", displayName: "Shared (Anthropic)" }, { id: "google-antigravity/shared-model", model: "google-antigravity/shared-model", displayName: "Shared (Google)" }, ]; }, async openSession(options) { openedSessions.push(options); const session = await sessions.openSession(options); const runTurn = session.runTurn.bind(session); session.runTurn = (prompt, context) => { turnModels.push(context.options.model); return runTurn(prompt, context); }; return session; }, }; return withHarness({ backendFactory, config: { defaultModel: "gpt-5.6-sol", modelAliases: { "claude-opus-5": "anthropic/claude-opus-5", "gemini-flash": "google-antigravity/gemini-3.7-flash", "gpt-5.6": "openai-codex/gpt-5.6-luna", "gpt-5.6-sol": "openai-codex/gpt-5.6-sol", luna: "openai-codex/gpt-5.6-luna", }, }, }, async (h) => { await h.initialize(); const config = await h.request("config/read", {}); assert.equal(config.config.model_provider, null); assert.deepEqual(config.config.model_providers, {}); const visible = await h.request("model/list", { includeHidden: false, limit: 100 }); assert.deepEqual( visible.data.slice(0, 4).map((model) => model.id), ["gpt-5.6-sol", "claude-opus-5", "gemini-3.7-flash", "gpt-5.6-luna"] );
const complete = await h.request("model/list", { includeHidden: true, limit: 100 }); assert.deepEqual( complete.data.slice(0, 4).map((model) => model.id), ["gpt-5.6-sol", "claude-opus-5", "gemini-3.7-flash", "gpt-5.6-luna"] ); assert.equal(complete.data.filter((model) => model.isDefault).length, 1); assert.equal(complete.data.find((model) => model.isDefault).id, "gpt-5.6-sol"); assert.equal(complete.data.some((model) => model.id === "anthropic/claude-opus-5"), false); assert.equal(complete.data.some((model) => model.id === "google-antigravity/gemini-3.7-flash"), false); assert.equal(complete.data.some((model) => ["gemini-flash", "gpt-5.6", "luna"].includes(model.id)), false); assert.ok(complete.data.every((model) => Object.hasOwn(model, "modelSpecialty"))); assert.ok(complete.data.every((model) => Object.hasOwn(model, "multiAgentVersion")));
const openaiStarted = await h.request("thread/start", { cwd: h.temp, model: "gpt-5.6-sol" }); assert.equal(openaiStarted.model, "gpt-5.6-sol"); assert.equal(openaiStarted.modelProvider, "openai"); assert.equal(openaiStarted.thread.modelProvider, "openai"); assert.equal(openaiStarted.thread.modelContextWindow, 1_000_000); assert.equal(openaiStarted.thread.modelSelector, "openai-codex/gpt-5.6-sol");
const qualifiedOpenAi = "openai-codex/gpt-5.6-sol"; const qualifiedStarted = await h.request("thread/start", { cwd: h.temp, model: qualifiedOpenAi }); assert.equal(qualifiedStarted.model, "gpt-5.6-sol"); assert.equal(qualifiedStarted.modelProvider, "openai"); assert.equal(qualifiedStarted.thread.model, "gpt-5.6-sol"); assert.equal(qualifiedStarted.thread.modelProvider, "openai"); assert.equal(qualifiedStarted.thread.modelSelector, qualifiedOpenAi); const qualifiedResumed = await h.request("thread/resume", { threadId: qualifiedStarted.thread.id, model: "anthropic/claude-opus-5", config: { model_reasoning_effort: "xhigh" }, }); assert.equal(qualifiedResumed.model, "claude-opus-5"); assert.equal(qualifiedResumed.thread.model, "claude-opus-5"); assert.equal(qualifiedResumed.thread.modelSelector, "anthropic/claude-opus-5"); assert.equal(qualifiedResumed.reasoningEffort, "xhigh"); assert.equal(qualifiedResumed.thread.reasoningEffort, "xhigh");
const collaborationStarted = await h.request("thread/start", { cwd: h.temp, collaborationMode: { mode: "default", settings: { model: "claude-opus-5", reasoning_effort: "high" } }, }); assert.equal(collaborationStarted.thread.modelSelector, "anthropic/claude-opus-5"); const collaborationResumed = await h.request("thread/resume", { threadId: collaborationStarted.thread.id, collaborationMode: { mode: "default", settings: { model: "google-antigravity/shared-model", reasoning_effort: "high" } }, }); assert.equal(collaborationResumed.thread.modelSelector, "google-antigravity/shared-model");
await h.request("thread/start", { cwd: h.temp, model: "claude-opus-5" }); assert.equal(openedSessions.at(-1).model, "anthropic/claude-opus-5");
const nested = "MercuriusDream/Nanbeige4.2-3B-mlx-8bit"; const started = await h.request("thread/start", { cwd: h.temp, model: nested }); assert.equal(started.model, nested); assert.equal(started.thread.model, nested); assert.equal(openedSessions.at(-1).model, nested); const { thread } = started; const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "nested model" }], }); await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); assert.equal(turnModels.at(-1), nested);
await h.request("thread/settings/update", { threadId: thread.id, model: "google-antigravity/shared-model", }); const settingsUpdate = h.messages.findLast((message) => message.method === "thread/settings/updated"); assert.equal(settingsUpdate.params.threadSettings.model, "shared-model"); assert.equal(settingsUpdate.params.threadSettings.modelProvider, "google-antigravity"); });});
test("provider-qualified defaults stay visible and the Desktop catalog stays within one page", () => { const canonical = { id: "gemini-3.7-flash", model: "gemini-3.7-flash", selector: "google-antigravity/gemini-3.7-flash", displayName: "Gemini 3.7 Flash", }; const qualified = { ...canonical, id: canonical.selector, model: canonical.selector, hidden: true, }; const backendFactory = { async listModels() { return [ qualified, canonical, ...Array.from({ length: 110 }, (_, index) => ({ id: `provider-model-${String(index).padStart(3, "0")}`, model: `provider-model-${String(index).padStart(3, "0")}`, displayName: `Provider Model ${index}`, })), ]; }, }; return withHarness({ backendFactory, config: { defaultModel: "google-antigravity/gemini-3.7-flash" }, }, async (h) => { await h.initialize(); const complete = await h.request("model/list", { includeHidden: true, limit: 100 }); assert.equal(complete.data.length, 111); assert.equal(complete.data[0].id, "gemini-3.7-flash"); assert.equal(complete.data[0].isDefault, true); assert.equal(complete.data[0].hidden, false); assert.equal(complete.data.slice(1).every((model) => model.hidden), true);
const visible = await h.request("model/list", { includeHidden: false, limit: 200 }); assert.equal(visible.data.length, 1); assert.equal(visible.data.filter((model) => model.isDefault).length, 1); });});
test("a turn streams a native agent item and completes", () => withHarness({}, async (h) => { await h.initialize(); const started = await h.request("thread/start", { cwd: h.temp, model: "mock/omp" }); const threadId = started.thread.id; const response = await h.request("turn/start", { threadId, input: [{ type: "text", text: "hello" }], }); const turnId = response.turn.id; await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turnId); const methods = h.messages.map((message) => message.method).filter(Boolean); assert.ok(methods.includes("turn/started")); assert.ok(methods.includes("item/started")); assert.ok(methods.includes("item/agentMessage/delta")); assert.ok(methods.includes("item/completed")); assert.ok(methods.includes("thread/tokenUsage/updated")); const usageMsg = h.messages.find((message) => message.method === "thread/tokenUsage/updated"); assert.ok(usageMsg.params.tokenUsage); assert.ok(usageMsg.params.tokenUsage.modelContextWindow > 0); const completed = h.messages.find((message) => message.method === "turn/completed" && message.params.turn.id === turnId); assert.equal(completed.params.turn.status, "completed"); const agent = completed.params.turn.items.find((item) => item.type === "agentMessage"); assert.match(agent.text, /Mock OMP response: hello/);}));
test("tool user-input requests use the complete Desktop schema and return answers", () => { const base = new MockBackendFactory(); let clientRequest = null; const backendFactory = { listModels: (...args) => base.listModels(...args), async openSession() { return { async runTurn(_prompt, context) { const answer = await context.requestInput({ itemId: "ask-item", questions: [{ id: "vsock", header: "vsock blocked", question: "Load vhost_vsock?", options: [{ label: "loaded", description: "Continue the test." }], }], }); await context.emit({ type: "textDelta", delta: `answer:${answer}` }); return { status: "completed" }; }, async getPersistentState() { return {}; }, async interrupt() {}, async dispose() {}, }; }, }; return withHarness({ backendFactory, clientResponse(message) { if (message.method === "item/tool/requestUserInput") { clientRequest = message; return { answers: { vsock: { answers: ["loaded"] } } }; } return { decision: "accept" }; }, }, async (h) => { await h.initialize(); const started = await h.request("thread/start", { cwd: h.temp, model: "mock/omp" }); const response = await h.request("turn/start", { threadId: started.thread.id, input: [{ type: "text", text: "ask me" }], }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params?.turn?.id === response.turn.id ); assert.ok(clientRequest); assert.deepEqual(clientRequest.params, { threadId: started.thread.id, turnId: response.turn.id, itemId: "ask-item", questions: [{ id: "vsock", header: "vsock blocked", question: "Load vhost_vsock?", isOther: true, isSecret: false, options: [{ label: "loaded", description: "Continue the test." }], }], isBlocking: true, autoResolutionMs: null, }); assert.match(completed.params.turn.items.find((item) => item.type === "agentMessage").text, /answer:loaded/); const resolved = h.messages.find((message) => message.method === "serverRequest/resolved" && message.params?.threadId === started.thread.id ); assert.deepEqual(resolved?.params, { threadId: started.thread.id, requestId: clientRequest.id, }); });});
test("a terminal session marker prevents cancelled external asks from blocking the next turn", () => withHarness({}, async (h) => { await h.initialize(); const sessionFile = path.join(h.temp, "cancelled-ask.jsonl"); await fs.writeFile(sessionFile, [ JSON.stringify({ type: "session", id: "cancelled-ask-owner", timestamp: new Date().toISOString(), cwd: h.temp }), JSON.stringify({ type: "title", title: "Cancelled ask" }), JSON.stringify({ type: "message", message: { role: "user", content: [{ type: "text", text: "first" }] } }), JSON.stringify({ type: "message", message: { role: "toolResult", toolName: "ask", isError: true, content: [{ type: "text", text: "Ask input was cancelled" }] } }), JSON.stringify({ type: "custom", customType: "session_exit", data: { reason: "sigterm" } }), ].join("\n") + "\n"); const thread = await h.store.createThread({ threadId: "cancelled-ask-thread", cwd: h.temp, preview: "Cancelled ask", status: { type: "active", activeFlags: [] }, }); await h.store.setBackendState(thread.id, { sessionId: "cancelled-ask-owner", sessionPath: sessionFile, resume: { sessionId: "cancelled-ask-owner", sessionPath: sessionFile }, });
const started = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "continue after cancellation" }], }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params?.turn?.id === started.turn.id ); assert.equal(completed.params.turn.status, "completed");}));test("interrupt aborts an active backend turn", () => withHarness({ chunkDelayMs: 15 }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "/slow" }] }); await h.waitFor((message) => message.method === "item/agentMessage/delta"); await h.request("turn/interrupt", { threadId: thread.id, turnId: turn.id }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); assert.equal(completed.params.turn.status, "interrupted");}));test("server shutdown persists deferred active-turn output", () => withHarness({ chunkDelayMs: 25 }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "/slow" }], }); await h.waitFor((message) => message.method === "item/agentMessage/delta"); await h.server.close();
const persisted = await new StateStore(h.store.file).getThread(thread.id); assert.equal(persisted.status.type, "idle"); const persistedTurn = persisted.turns.find((candidate) => candidate.id === turn.id); assert.equal(persistedTurn.status, "interrupted"); assert.ok(persistedTurn.items.find((item) => item.type === "agentMessage")?.text);}));
test("interrupted rich tool items complete with a valid failed status", () => { let disposeCount = 0; const backendFactory = { async listModels() { return [{ id: "mock/omp", model: "mock/omp" }]; }, async openSession() { return { async runTurn(_prompt, context) { const aborted = new Promise((resolve) => context.signal.addEventListener("abort", resolve, { once: true })); await context.emit({ type: "toolStart", itemType: "dynamicToolCall", toolCallId: "dynamic-1", toolName: "read", args: { path: "file.txt" } }); await aborted; return { status: "interrupted" }; }, async interrupt() {}, async dispose() { disposeCount += 1; }, }; }, }; return withHarness({ backendFactory }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "inspect" }] }); await h.waitFor((message) => message.method === "item/started" && message.params.item.type === "dynamicToolCall"); await h.request("turn/interrupt", { threadId: thread.id, turnId: turn.id }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); assert.equal(completed.params.turn.items.find((item) => item.type === "dynamicToolCall").status, "failed"); assert.equal(disposeCount, 1); });});
test("a delayed interrupt cannot cancel a newer turn", () => withHarness({ chunkDelayMs: 15 }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn: firstTurn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "first" }] }); await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === firstTurn.id);
const { turn: secondTurn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "/slow" }] }); await h.waitFor((message) => message.method === "item/agentMessage/delta" && message.params.turnId === secondTurn.id); await assert.rejects( h.request("turn/interrupt", { threadId: thread.id, turnId: firstTurn.id }), /Turn not found/ ); assert.equal(h.messages.some((message) => message.method === "turn/completed" && message.params.turn.id === secondTurn.id), false);
await h.request("turn/interrupt", { threadId: thread.id, turnId: secondTurn.id }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === secondTurn.id); assert.equal(completed.params.turn.status, "interrupted");}));
test("ephemeral title sessions dispose their backend exactly once", () => { let disposeCount = 0; const backendFactory = { async listModels() { return [{ id: "mock/omp", model: "mock/omp" }]; }, async openSession() { return { async runTurn(prompt, context) { const interrupted = String(prompt).includes("interrupt"); const aborted = interrupted ? new Promise((resolve) => context.signal.addEventListener("abort", resolve, { once: true })) : null; await context.emit({ type: "textDelta", delta: "short title" }); if (aborted) { await aborted; return { status: "interrupted" }; } return { status: "completed" }; }, async interrupt() {}, async dispose() { disposeCount += 1; }, }; }, }; return withHarness({ backendFactory }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp, ephemeral: true }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "title" }] }); await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); assert.equal(disposeCount, 1);
const { thread: interruptedThread } = await h.request("thread/start", { cwd: h.temp, ephemeral: true }); const { turn: interruptedTurn } = await h.request("turn/start", { threadId: interruptedThread.id, input: [{ type: "text", text: "interrupt" }] }); await h.waitFor((message) => message.method === "item/agentMessage/delta" && message.params.turnId === interruptedTurn.id); await h.request("turn/interrupt", { threadId: interruptedThread.id, turnId: interruptedTurn.id }); await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === interruptedTurn.id); assert.equal(disposeCount, 2); });});
test("command approval is a server-initiated request", () => withHarness({ approval: "accept" }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "/approval" }] }); const approval = await h.waitFor((message) => message.method === "item/commandExecution/requestApproval"); assert.equal(approval.params.threadId, thread.id); assert.equal(approval.params.turnId, turn.id); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); const command = completed.params.turn.items.find((item) => item.type === "commandExecution"); assert.equal(command.status, "completed"); assert.match(command.aggregatedOutput, /bridge tool output/);}));
test("file changes use native fileChange items and diff notifications", () => withHarness({ approval: "accept" }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "/file" }] }); await h.waitFor((message) => message.method === "item/fileChange/requestApproval"); const diff = await h.waitFor((message) => message.method === "turn/diff/updated"); assert.match(diff.params.diff, /hello from OMP/); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); assert.equal(completed.params.turn.items.find((item) => item.type === "fileChange").status, "completed");}));
test("thread goals persist, notify Desktop, account usage, and clear", () => withHarness({}, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const set = await h.request("thread/goal/set", { threadId: thread.id, objective: "finish rich protocol support", status: "active", tokenBudget: 500, }); assert.equal(set.goal.threadId, thread.id); assert.equal(set.goal.objective, "finish rich protocol support"); assert.equal(set.goal.status, "active"); assert.equal(set.goal.tokenBudget, 500); assert.ok(set.goal.createdAt < 10_000_000_000); assert.ok(set.goal.updatedAt < 10_000_000_000); const messagesBeforeResume = h.messages.length; await h.request("thread/resume", { threadId: thread.id }); await new Promise((resolve) => setTimeout(resolve, 10)); assert.ok(h.messages.slice(messagesBeforeResume).some((message) => message.method === "thread/goal/updated" && message.params.goal.objective === set.goal.objective )); assert.ok(h.messages.some((message) => message.method === "thread/goal/updated" && message.params.goal.objective === set.goal.objective));
const read = await h.request("thread/goal/get", { threadId: thread.id }); assert.deepEqual(read.goal, set.goal);
const forked = await h.request("thread/fork", { threadId: thread.id, cwd: h.temp }); assert.equal((await h.request("thread/goal/get", { threadId: forked.thread.id })).goal, null); const deferredFork = await h.request("thread/fork", { threadId: thread.id, cwd: h.temp, deferGoalContinuation: true, }); const forkedGoal = await h.request("thread/goal/get", { threadId: deferredFork.thread.id }); assert.equal(forkedGoal.goal.threadId, deferredFork.thread.id); assert.equal(forkedGoal.goal.objective, set.goal.objective); await assert.rejects( h.request("thread/fork", { threadId: thread.id, deferGoalContinuation: true, ephemeral: true }), /cannot be combined/ );
const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "continue" }], }); await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); const accounted = await h.request("thread/goal/get", { threadId: thread.id }); assert.ok(accounted.goal.tokensUsed > 0); assert.ok(accounted.goal.updatedAt >= set.goal.updatedAt); const budgetLimited = await h.request("thread/goal/set", { threadId: thread.id, objective: null, status: "active", tokenBudget: accounted.goal.tokensUsed, }); assert.equal(budgetLimited.goal.objective, set.goal.objective); assert.equal(budgetLimited.goal.status, "budgetLimited");
const cleared = await h.request("thread/goal/clear", { threadId: thread.id }); assert.equal(cleared.cleared, true); assert.equal((await h.request("thread/goal/get", { threadId: thread.id })).goal, null); assert.ok(h.messages.some((message) => message.method === "thread/goal/cleared" && message.params.threadId === thread.id));}));
test("goal accounting includes every model call in a tool-using turn", () => { const backendFactory = { async listModels() { return [{ id: "mock/omp", model: "mock/omp" }]; }, async openSession() { return { async runTurn(_prompt, context) { await context.emit({ type: "tokenUsage", usage: { inputTokens: 20, outputTokens: 10, totalTokens: 30 } }); await context.emit({ type: "tokenUsage", contextUsage: { tokens: 900_000, contextWindow: 1_000_000 } }); await context.emit({ type: "tokenUsage", usage: { inputTokens: 30, outputTokens: 10, totalTokens: 40 } }); return { status: "completed" }; }, async interrupt() {}, async dispose() {}, }; }, }; return withHarness({ backendFactory }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); await h.request("thread/goal/set", { threadId: thread.id, objective: "account all calls" }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "continue" }] }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); assert.equal(completed.params.turn.tokenUsage.totalTokens, 70); assert.equal((await h.request("thread/goal/get", { threadId: thread.id })).goal.tokensUsed, 70); });});
test("rich OMP tools use native Desktop item types and plan UI", () => { const backendFactory = { async listModels() { return [{ id: "mock/omp", model: "mock/omp" }]; }, async openSession() { return { async runTurn(_prompt, context) { await context.emit({ type: "toolStart", itemType: "dynamicToolCall", toolCallId: "read-1", toolName: "read", namespace: "oh-my-pi", args: { path: "src/app-server.mjs" } }); await context.emit({ type: "toolEnd", toolCallId: "read-1", status: "completed", outputText: "file contents", output: { content: [{ type: "text", text: "file contents" }, { type: "image", data: "aGVsbG8=", mimeType: "image/png" }] }, durationMs: 4, }); await context.emit({ type: "toolStart", itemType: "webSearch", toolCallId: "web-1", toolName: "web_search", args: { query: "Codex protocol" } }); await context.emit({ type: "toolEnd", toolCallId: "web-1", status: "completed", output: { results: [{ title: "Protocol" }] } }); await context.emit({ type: "toolStart", itemType: "mcpToolCall", toolCallId: "mcp-1", toolName: "lookup", server: "docs", args: { id: 1 } }); await context.emit({ type: "toolDelta", toolCallId: "mcp-1", delta: "working" }); await context.emit({ type: "toolEnd", toolCallId: "mcp-1", status: "completed", outputText: "done", output: { content: [{ type: "text", text: "rendered" }], details: { rawContent: [{ type: "text", text: "done" }, { type: "resource", resource: { uri: "docs://one", text: "resource" } }], mcpMeta: { trace: "abc" }, }, }, durationMs: 3, }); await context.emit({ type: "toolStart", itemType: "imageView", toolCallId: "image-1", toolName: "inspect_image", args: { path: "image.png" } }); await context.emit({ type: "toolEnd", toolCallId: "image-1", status: "completed" }); await context.emit({ type: "planUpdate", explanation: "native plan", plan: [{ step: "verify UI", status: "inProgress" }] }); return { status: "completed" }; }, async interrupt() {}, async dispose() {}, }; }, }; return withHarness({ backendFactory }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "use tools" }] }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); const types = completed.params.turn.items.map((item) => item.type); assert.ok(types.includes("dynamicToolCall")); assert.ok(types.includes("webSearch")); assert.ok(types.includes("mcpToolCall")); assert.ok(types.includes("imageView")); const dynamic = completed.params.turn.items.find((item) => item.type === "dynamicToolCall"); assert.deepEqual(dynamic.contentItems, [ { type: "inputText", text: "file contents" }, { type: "inputImage", imageUrl: "data:image/png;base64,aGVsbG8=" }, ]); const mcp = completed.params.turn.items.find((item) => item.type === "mcpToolCall"); assert.equal(mcp.result.content[1].type, "resource"); assert.deepEqual(mcp.result._meta, { trace: "abc" }); assert.equal(completed.params.turn.items.find((item) => item.type === "imageView").status, undefined); assert.ok(h.messages.some((message) => message.method === "item/mcpToolCall/progress")); assert.deepEqual(h.messages.find((message) => message.method === "turn/plan/updated").params.plan, [ { step: "verify UI", status: "inProgress" }, ]); });});
test("thread metadata persists and paginates", () => withHarness({}, async (h) => { await h.initialize(); const one = await h.request("thread/start", { cwd: h.temp }); const two = await h.request("thread/start", { cwd: h.temp }); await h.request("thread/name/set", { threadId: one.thread.id, name: "persisted name" }); const page1 = await h.request("thread/list", { limit: 1 }); assert.equal(page1.data.length, 1); assert.ok(page1.nextCursor); const page2 = await h.request("thread/list", { limit: 1, cursor: page1.nextCursor }); assert.equal(page2.data.length, 1); assert.notEqual(page1.data[0].id, page2.data[0].id); const read = await h.request("thread/read", { threadId: one.thread.id }); assert.equal(read.thread.name, "persisted name"); assert.equal(page1.data[0].id, one.thread.id);}));
test("unknown methods fail explicitly", () => withHarness({}, async (h) => { await h.initialize(); const id = 999; await h.server.handle(h.peer, { id, method: "unknown/method", params: {} }); const response = h.messages.find((message) => message.id === id); assert.equal(response.error.code, -32601);}));test("thread/compact/start triggers contextCompaction turn and returns immediately", () => withHarness({}, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const compactResponse = await h.request("thread/compact/start", { threadId: thread.id }); assert.deepEqual(compactResponse, {}); const completed = await h.waitFor((message) => message.method === "turn/completed"); const compactionItem = completed.params.turn.items.find((item) => item.type === "contextCompaction"); assert.ok(compactionItem); const compactedNotif = h.messages.find((message) => message.method === "thread/compacted"); assert.ok(compactedNotif); assert.equal(compactedNotif.params.threadId, thread.id);}));test("thread/fork creates a child thread and copies turns", () => withHarness({}, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const turn1 = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "turn 1" }] }); await h.waitFor((m) => m.method === "turn/completed" && m.params.turn.id === turn1.turn.id); const turn2 = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "turn 2" }] }); await h.waitFor((m) => m.method === "turn/completed" && m.params.turn.id === turn2.turn.id);
const forked = await h.request("thread/fork", { threadId: thread.id, lastTurnId: turn1.turn.id }); assert.equal(forked.thread.forkedFromId, thread.id); assert.equal(forked.thread.parentThreadId, thread.id); assert.equal(forked.thread.turns.length, 1); assert.equal(forked.thread.turns[0].id, turn1.turn.id);
const sideFork = await h.request("thread/fork", { threadId: thread.id, cwd: h.temp, excludeTurns: true, ephemeral: true, }); assert.equal(sideFork.thread.turns.length, 0);
const boundary = { type: "message", role: "user", content: [{ type: "input_text", text: "Side conversation boundary." }], }; assert.deepEqual(await h.request("thread/inject_items", { threadId: sideFork.thread.id, items: [boundary], }), {}); assert.deepEqual(h.server.sessions.get(sideFork.thread.id).injectedItems, [boundary]);}));test("thread resume returns the requested recent page and paginates older turns", () => withHarness({}, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const turnIds = []; for (let index = 1; index <= 3; index += 1) { const started = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: `turn ${index}` }], }); turnIds.push(started.turn.id); await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === started.turn.id); }
const resumed = await h.request("thread/resume", { threadId: thread.id, excludeTurns: true, initialTurnsPage: { limit: 2, itemsView: "full", sortDirection: "desc" }, }); assert.deepEqual(resumed.thread.turns, []); assert.deepEqual(resumed.initialTurnsPage.data.map((turn) => turn.id), [turnIds[2], turnIds[1]]); assert.ok(resumed.initialTurnsPage.data.every((turn) => turn.itemsView === "full" && turn.items.length > 0)); assert.ok(resumed.initialTurnsPage.nextCursor);
const older = await h.request("thread/turns/list", { threadId: thread.id, cursor: resumed.initialTurnsPage.nextCursor, limit: 2, itemsView: "notLoaded", sortDirection: "desc", }); assert.deepEqual(older.data.map((turn) => turn.id), [turnIds[0]]); assert.deepEqual(older.data[0].items, []); assert.equal(older.data[0].itemsView, "notLoaded"); assert.equal(older.nextCursor, null); assert.ok(older.backwardsCursor); const reversed = await h.request("thread/turns/list", { threadId: thread.id, cursor: older.backwardsCursor, limit: 2, itemsView: "notLoaded", sortDirection: "asc", }); assert.deepEqual(reversed.data.map((turn) => turn.id), [turnIds[0], turnIds[1]]);
const summary = await h.request("thread/turns/list", { threadId: thread.id, limit: 1, sortDirection: "desc", }); assert.equal(summary.data[0].itemsView, "summary"); assert.deepEqual(summary.data[0].items.map((item) => item.type), ["userMessage", "agentMessage"]);
const items = await h.request("thread/items/list", { threadId: thread.id, turnId: turnIds[0], limit: 100, }); assert.ok(items.data.some((entry) => entry.turnId === turnIds[0] && entry.item.type === "userMessage")); assert.equal(items.nextCursor, null); assert.ok(items.backwardsCursor);
const allItems = await h.request("thread/items/list", { threadId: thread.id, limit: 2, sortDirection: "desc", }); assert.equal(allItems.data.length, 2); assert.ok(allItems.data.every((entry) => entry.turnId && entry.item?.id)); assert.ok(allItems.nextCursor);
await assert.rejects( h.request("thread/turns/list", { threadId: thread.id, cursor: "invalid", limit: 1 }), /Invalid thread-turn cursor/ );}));test("thread/rollback drops turns and returns updated thread", () => withHarness({}, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const turn1 = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "turn 1" }] }); await h.waitFor((m) => m.method === "turn/completed" && m.params.turn.id === turn1.turn.id); const turn2 = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "turn 2" }] }); await h.waitFor((m) => m.method === "turn/completed" && m.params.turn.id === turn2.turn.id);
const rolledBack = await h.request("thread/rollback", { threadId: thread.id, numTurns: 1 }); assert.equal(rolledBack.thread.turns.length, 1); assert.equal(rolledBack.thread.turns[0].id, turn1.turn.id);}));test("account/login/start and account/logout flow cleanly", () => withHarness({}, async (h) => { await h.initialize(); const loginRes = await h.request("account/login/start", { type: "chatgptAuthTokens" }); assert.equal(loginRes.type, "chatgptAuthTokens"); const completed = await h.waitFor((m) => m.method === "account/login/completed"); assert.equal(completed.params.success, true); const updated = await h.waitFor((m) => m.method === "account/updated"); assert.equal(updated.params.authMode, "apikey"); const logoutRes = await h.request("account/logout", {}); assert.deepEqual(logoutRes, {});}));
test("config/read returns a schema-complete typed config for partial user TOML", () => withHarness({}, async (h) => { const codexHome = path.join(h.temp, "codex-home"); await fs.mkdir(codexHome, { recursive: true }); await fs.writeFile(path.join(codexHome, "config.toml"), [ "[features]", "js_repl = true", "", "[memories]", "use_memories = false", "", "[shell_environment_policy.set]", "EXAMPLE = \"1\"", "", "[tui]", "notifications = false", "", "[tui.model_availability_nux]", "\"gpt-5.6-sol\" = 1", "", "[hooks.state.\"/tmp/hooks.json:pre_compact:0:0\"]", "trusted_hash = \"sha256:abc\"", "", "[marketplaces.openai-bundled]", "source_type = \"local\"", "source = \"/tmp/bundled\"", "", "[mcp_servers.node_repl]", "command = \"/usr/bin/true\"", "", "[model_providers.empty-provider]", "", ].join("\n")); const previousHome = process.env.CODEX_HOME; process.env.CODEX_HOME = codexHome; try { await h.initialize(); const { config, ...envelope } = await h.request("config/read", { cwd: h.temp });
assert.deepEqual(Object.keys(envelope), ["origins"]); for (const bridgeOnly of ["backend", "stateFile", "configFile", "cwd", "modelAliases", "defaultModel"]) { assert.equal(Object.hasOwn(config, bridgeOnly), false, `${bridgeOnly} must not leak into the typed config`); }
assert.deepEqual(Object.keys(config.features).sort(), [ "auth_elicitation", "js_repl", "memories", "mentions_v2", "network_proxy", "remote_control", "remote_plugin", "tool_suggest", ]); assert.equal(config.features.js_repl, true);
assert.equal(config.memories.use_memories, false); assert.equal(Object.keys(config.memories).length, 12); assert.equal(Object.hasOwn(config.memories, "consolidation_model"), true);
assert.deepEqual(Object.keys(config.shell_environment_policy).sort(), [ "exclude", "experimental_use_profile", "ignore_default_excludes", "include_only", "inherit", "set", ]); assert.deepEqual(config.shell_environment_policy.set, { EXAMPLE: "1" });
assert.equal(config.tui.notifications, false); assert.equal(config.tui.pet_anchor, "composer"); assert.deepEqual(config.tui.model_availability_nux, { "gpt-5.6-sol": 1 }); assert.equal(Object.hasOwn(config.tui, "terminal_resize_reflow_max_rows"), true); assert.equal(Object.hasOwn(config.tui.keymap.global, "open_transcript"), true); assert.equal(Object.hasOwn(config.tui.keymap.vim_text_object, "backtick"), true);
assert.deepEqual(config.hooks.PreToolUse, []); assert.deepEqual(config.hooks.Stop, []); assert.deepEqual(config.hooks.state["/tmp/hooks.json:pre_compact:0:0"], { trusted_hash: "sha256:abc" });
assert.deepEqual(config.marketplaces["openai-bundled"], { last_updated: null, last_revision: null, source_type: "local", source: "/tmp/bundled", ref: null, sparse_paths: null, }); assert.deepEqual(config.mcp_servers.node_repl, { command: "/usr/bin/true", args: [], environment_id: "local", enabled: true, tool_timeout_sec: null, });
assert.equal(config.model_provider, null, "an incomplete provider table must not be advertised");
const withLayers = await h.request("config/read", { cwd: h.temp, includeLayers: true }); assert.equal(Array.isArray(withLayers.layers), true); } finally { if (previousHome === undefined) delete process.env.CODEX_HOME; else process.env.CODEX_HOME = previousHome; }}));
test("a compaction turn is stored as completed, not left running forever", () => withHarness({}, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); await h.request("thread/compact/start", { threadId: thread.id }); await h.waitFor((message) => message.method === "thread/compacted"); await h.waitFor((message) => message.method === "turn/completed"); const stored = await h.store.getThread(thread.id); const compaction = stored.turns.find((turn) => (turn.items || []).some((item) => item.type === "contextCompaction")); assert.equal(compaction.status, "completed"); assert.ok(compaction.completedAt);}));
test("resuming a thread settles turns nobody is running and reports idle", () => withHarness({}, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); await h.store.addTurn(thread.id, { id: "turn_abandoned", status: "inProgress", startedAt: 1, completedAt: null, items: [ { id: "user-1", type: "userMessage", content: [{ type: "text", text: "hi" }], status: "completed" }, { id: "cmd-1", type: "commandExecution", command: "read a.ts", status: "inProgress", exitCode: null, durationMs: null }, ], }); await h.store.updateThread(thread.id, (stored) => { stored.status = { type: "active", activeFlags: [] }; });
const resumed = await h.request("thread/resume", { threadId: thread.id }); assert.deepEqual(resumed.thread.status, { type: "idle" }); const settled = (await h.store.getThread(thread.id)).turns.find((turn) => turn.id === "turn_abandoned"); assert.equal(settled.status, "interrupted"); assert.equal(settled.items.find((item) => item.id === "cmd-1").status, "failed"); assert.equal(settled.items.find((item) => item.id === "cmd-1").exitCode, 1); const announced = await h.waitFor((message) => message.method === "turn/completed" && message.params?.turn?.id === "turn_abandoned"); assert.equal(announced.params.turn.status, "interrupted");}));
test("a live turn is never settled out from under its client", () => withHarness({ chunkDelayMs: 40 }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "work" }] }); await h.request("thread/resume", { threadId: thread.id }); const live = (await h.store.getThread(thread.id)).turns.find((candidate) => candidate.id === turn.id); assert.equal(live.status, "inProgress"); await h.waitFor((message) => message.method === "turn/completed" && message.params?.turn?.id === turn.id);}));
test("an answer that arrives whole instead of as deltas still reaches the client", () => { const backendFactory = { async listModels() { return [{ id: "mock/omp", model: "mock/omp" }]; }, async openSession() { return { async runTurn(_prompt, context) { await context.emit({ type: "reasoningDelta", delta: "**Weighing it up**" }); await context.emit({ type: "assistantThinking", thinking: "**Weighing it up**\nmore detail" }); await context.emit({ type: "assistantText", text: "yep, tantivy does the heavy lifting" }); return { status: "completed" }; }, async interrupt() {}, async dispose() {}, }; }, }; return withHarness({ backendFactory }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "ask" }] }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); const items = completed.params.turn.items; const agent = items.filter((item) => item.type === "agentMessage"); assert.equal(agent.length, 1, `expected exactly one answer, got ${JSON.stringify(items.map((i) => i.type))}`); assert.equal(agent[0].text, "yep, tantivy does the heavy lifting"); const reasoning = items.find((item) => item.type === "reasoning"); assert.deepEqual(reasoning.summary, ["**Weighing it up**\nmore detail"], "the closing block must extend, not duplicate, the streamed thinking"); assert.deepEqual(reasoning.content, [], "omp thinking is a summary, not raw chain-of-thought"); const delta = h.messages.find((message) => message.method === "item/agentMessage/delta"); assert.equal(delta.params.delta, "yep, tantivy does the heavy lifting", "clients must see the answer stream in"); });});
test("a streamed answer confirmed by its closing block is not duplicated", () => { const backendFactory = { async listModels() { return [{ id: "mock/omp", model: "mock/omp" }]; }, async openSession() { return { async runTurn(_prompt, context) { await context.emit({ type: "textDelta", delta: "half " }); await context.emit({ type: "textDelta", delta: "an answer" }); await context.emit({ type: "assistantText", text: "half an answer" }); return { status: "completed" }; }, async interrupt() {}, async dispose() {}, }; }, }; return withHarness({ backendFactory }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "ask" }] }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); const agent = completed.params.turn.items.filter((item) => item.type === "agentMessage"); assert.equal(agent.length, 1); assert.equal(agent[0].text, "half an answer"); });});
test("cumulative answer snapshots append only their unseen tails", () => { const backendFactory = { async listModels() { return [{ id: "mock/omp", model: "mock/omp" }]; }, async openSession() { return { async runTurn(_prompt, context) { await context.emit({ type: "assistantText", text: "nothing," }); await context.emit({ type: "assistantText", text: "nothing, just a typo." }); await context.emit({ type: "assistantText", text: "nothing, just a typo. i meant “moved.”" }); await context.emit({ type: "assistantMessageEnd" }); return { status: "completed" }; }, async interrupt() {}, async dispose() {}, }; }, }; return withHarness({ backendFactory }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "ask" }] }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); const agent = completed.params.turn.items.filter((item) => item.type === "agentMessage"); assert.equal(agent.length, 1); assert.equal(agent[0].text, "nothing, just a typo. i meant “moved.”"); });});
test("terminal backend errors use the native error UI and fail the turn", () => { const backendFactory = { async listModels() { return [{ id: "mock/omp", model: "mock/omp" }]; }, async openSession() { return { async runTurn(_prompt, context) { await context.emit({ type: "turnError", message: "provider rejected the API key with 401 unauthorized", additionalDetails: "credential 2 of 2", willRetry: false, terminal: true, }); throw new Error("OMP RPC process exited after reporting the provider error"); }, async interrupt() {}, async dispose() {}, }; }, }; return withHarness({ backendFactory }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "ask" }] }); const error = await h.waitFor((message) => message.method === "error" && message.params.turnId === turn.id); assert.equal(error.params.willRetry, false); assert.equal(error.params.error.codexErrorInfo, "unauthorized"); assert.equal(error.params.error.additionalDetails, "credential 2 of 2"); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); assert.equal(completed.params.turn.status, "failed"); assert.deepEqual(completed.params.turn.error, error.params.error); assert.equal(h.messages.filter((message) => message.method === "error" && message.params?.turnId === turn.id).length, 1); });});
test("thinking from several messages in one turn is neither doubled nor glued", () => { const backendFactory = { async listModels() { return [{ id: "mock/omp", model: "mock/omp" }]; }, async openSession() { return { async runTurn(_prompt, context) { await context.emit({ type: "reasoningDelta", delta: "**Verifying rustfmt availability**" }); await context.emit({ type: "assistantThinking", thinking: "**Verifying rustfmt availability**" }); await context.emit({ type: "assistantMessageEnd" }); await context.emit({ type: "toolStart", itemType: "commandExecution", toolCallId: "cmd-1", toolName: "bash", command: "which rustfmt" }); await context.emit({ type: "toolEnd", toolCallId: "cmd-1", status: "completed", outputText: "" }); await context.emit({ type: "reasoningDelta", delta: "**Running cargo build**" }); await context.emit({ type: "assistantThinking", thinking: "**Running cargo build**" }); await context.emit({ type: "assistantText", text: "built it" }); await context.emit({ type: "assistantMessageEnd" }); return { status: "completed" }; }, async interrupt() {}, async dispose() {}, }; }, }; return withHarness({ backendFactory }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "format it" }] }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); const reasoning = completed.params.turn.items.filter((item) => item.type === "reasoning"); assert.equal(reasoning.length, 1, "one turn must keep one stable thinking card across tools"); assert.deepEqual(reasoning[0].summary, [ "**Verifying rustfmt availability**", "**Running cargo build**", ]); assert.deepEqual(reasoning[0].content, []); assert.equal(h.messages.some((message) => message.method === "item/reasoning/textDelta"), false); const summaryDeltas = h.messages.filter((message) => message.method === "item/reasoning/summaryTextDelta"); assert.deepEqual(summaryDeltas.map((message) => message.params.summaryIndex), [0, 1]); assert.equal(new Set(summaryDeltas.map((message) => message.params.itemId)).size, 1); const partAdded = h.messages.find((message) => message.method === "item/reasoning/summaryPartAdded"); assert.equal(partAdded.params.summaryIndex, 1); assert.equal(completed.params.turn.items.filter((item) => item.type === "agentMessage").length, 1); });});
test("a closing block that arrives after its item closed does not reopen it", () => { const backendFactory = { async listModels() { return [{ id: "mock/omp", model: "mock/omp" }]; }, async openSession() { return { async runTurn(_prompt, context) { await context.emit({ type: "reasoningDelta", delta: "**Checking API visibility**" }); await context.emit({ type: "toolStart", itemType: "commandExecution", toolCallId: "cmd-1", toolName: "bash", command: "jj status" }); await context.emit({ type: "toolEnd", toolCallId: "cmd-1", status: "completed", outputText: "" }); // late closing blocks must not seed the next reasoning card await context.emit({ type: "assistantThinking", thinking: "**Checking API visibility**" }); await context.emit({ type: "assistantMessageEnd" }); await context.emit({ type: "reasoningDelta", delta: "**Planning the rewrite**" }); await context.emit({ type: "assistantMessageEnd" }); return { status: "completed" }; }, async interrupt() {}, async dispose() {}, }; }, }; return withHarness({ backendFactory }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "go" }] }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); const reasoning = completed.params.turn.items.filter((item) => item.type === "reasoning"); assert.equal(reasoning.length, 1); assert.deepEqual(reasoning[0].summary, [ "**Checking API visibility**", "**Planning the rewrite**", ]); assert.deepEqual(reasoning[0].content, []); });});
test("a closing thinking snapshot after message end is not projected twice", () => { const backendFactory = { async listModels() { return [{ id: "mock/omp", model: "mock/omp" }]; }, async openSession() { return { async runTurn(_prompt, context) { await context.emit({ type: "reasoningDelta", delta: "**Checking the protocol**" }); await context.emit({ type: "assistantMessageEnd" }); await context.emit({ type: "assistantThinking", thinking: "**Checking the protocol**" }); return { status: "completed" }; }, async interrupt() {}, async dispose() {}, }; }, }; return withHarness({ backendFactory }, async (h) => { await h.initialize(); const { thread } = await h.request("thread/start", { cwd: h.temp }); const { turn } = await h.request("turn/start", { threadId: thread.id, input: [{ type: "text", text: "check" }] }); const completed = await h.waitFor((message) => message.method === "turn/completed" && message.params.turn.id === turn.id); const reasoning = completed.params.turn.items.filter((item) => item.type === "reasoning"); assert.equal(reasoning.length, 1); assert.deepEqual(reasoning[0].summary, ["**Checking the protocol**"]); assert.equal(h.messages.filter((message) => message.method === "item/reasoning/summaryTextDelta").length, 1); });});