import test from "node:test"; import assert from "node:assert/strict"; import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { spawnSync } from "node:child_process"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); test("codex app-server protocol snapshot script records schema, help and method inventory", async () => { const runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-codex-protocol-snapshot-")); const fakeBin = path.join(runtimeRoot, "codex"); const outDir = path.join(runtimeRoot, "snapshot"); await writeFile( fakeBin, `#!/usr/bin/env node const fs = require("node:fs"); const path = require("node:path"); const args = process.argv.slice(2); function writeGenerated(out, name, content) { fs.mkdirSync(out, { recursive: true }); fs.writeFileSync(path.join(out, name), content); } if (args.includes("--version")) { console.log("codex-cli 0.135.0-alpha.1"); process.exit(0); } if (args[0] === "app-server" && args.includes("--help")) { console.log("Usage: codex app-server [OPTIONS]"); console.log("--listen stdio:// unix:// ws://IP:PORT off"); process.exit(0); } if (args[0] === "app-server" && args[1] === "generate-json-schema") { const out = args[args.indexOf("--out") + 1]; writeGenerated(out, "codex_app_server_protocol.schemas.json", JSON.stringify({ anyOf: [ { properties: { method: { const: "thread/start" } } }, { properties: { method: { const: "thread/inject_items" } } }, { properties: { method: { const: "thread/archive" } } }, { properties: { method: { const: "thread/unarchive" } } }, { properties: { method: { const: "thread/fork" } } }, { properties: { method: { const: "thread/compact/start" } } }, { properties: { method: { const: "thread/rollback" } } }, { properties: { method: { const: "thread/name/set" } } }, { properties: { method: { const: "thread/metadata/update" } } }, { properties: { method: { const: "thread/shellCommand" } } }, { properties: { method: { const: "thread/unsubscribe" } } }, { properties: { method: { const: "plugin/install" } } }, { properties: { method: { const: "plugin/uninstall" } } }, { properties: { method: { const: "plugin/read" } } }, { properties: { method: { const: "plugin/skill/read" } } }, { properties: { method: { const: "plugin/share/save" } } }, { properties: { method: { const: "plugin/share/checkout" } } }, { properties: { method: { const: "plugin/share/delete" } } }, { properties: { method: { const: "plugin/share/updateTargets" } } }, { properties: { method: { const: "plugin/share/list" } } }, { properties: { method: { const: "account/login/start" } } }, { properties: { method: { const: "account/login/cancel" } } }, { properties: { method: { const: "account/login/completed" } } }, { properties: { method: { const: "account/logout" } } }, { properties: { method: { const: "account/chatgptAuthTokens/refresh" } } }, { properties: { method: { const: "account/sendAddCreditsNudgeEmail" } } }, { properties: { method: { const: "config/value/write" } } }, { properties: { method: { const: "config/batchWrite" } } }, { properties: { method: { const: "config/mcpServer/reload" } } }, { properties: { method: { const: "skills/config/write" } } }, { properties: { method: { const: "command/exec" } } }, { properties: { method: { const: "command/exec/write" } } }, { properties: { method: { const: "command/exec/resize" } } }, { properties: { method: { const: "command/exec/terminate" } } }, { properties: { method: { const: "fs/readFile" } } }, { properties: { method: { const: "fs/readDirectory" } } }, { properties: { method: { const: "fs/getMetadata" } } }, { properties: { method: { const: "fs/writeFile" } } }, { properties: { method: { const: "fs/createDirectory" } } }, { properties: { method: { const: "fs/remove" } } }, { properties: { method: { const: "fs/copy" } } }, { properties: { method: { const: "fs/watch" } } }, { properties: { method: { const: "fs/unwatch" } } }, { properties: { method: { const: "externalAgentConfig/detect" } } }, { properties: { method: { const: "externalAgentConfig/import" } } }, { properties: { method: { const: "externalAgentConfig/import/completed" } } }, { properties: { method: { const: "marketplace/add" } } }, { properties: { method: { const: "marketplace/remove" } } }, { properties: { method: { const: "marketplace/upgrade" } } }, { properties: { method: { const: "experimentalFeature/list" } } }, { properties: { method: { const: "experimentalFeature/enablement/set" } } }, { properties: { method: { const: "review/start" } } }, { properties: { method: { const: "windowsSandbox/readiness" } } }, { properties: { method: { const: "windowsSandbox/setupStart" } } }, { properties: { method: { const: "windowsSandbox/setupCompleted" } } }, { properties: { method: { const: "fuzzyFileSearch/sessionUpdated" } } }, { properties: { method: { const: "fuzzyFileSearch/sessionCompleted" } } }, { properties: { method: { const: "skills/extraRoots/set" } } }, { properties: { method: { const: "hooks/list" } } }, { properties: { method: { const: "turn/interrupt" } } }, { properties: { method: { const: "turn/start" } } } ] }, null, 2)); process.exit(0); } if (args[0] === "app-server" && args[1] === "generate-ts") { const out = args[args.indexOf("--out") + 1]; writeGenerated(out, "ClientRequest.ts", 'export type ClientRequest = { "method": "thread/start" } | { "method": "skills/extraRoots/set" } | { "method": "hooks/list" } | { "method": "turn/start" };\\n'); process.exit(0); } console.error("unexpected args " + args.join(" ")); process.exit(2); `, "utf8", ); await chmod(fakeBin, 0o755); try { const result = spawnSync( process.execPath, [ "scripts/codex-app-server-protocol-snapshot.mjs", "--codex-bin", fakeBin, "--out-dir", outDir, ], { cwd: repoRoot, encoding: "utf8", }, ); assert.equal(result.status, 0, result.stderr); const manifest = JSON.parse(await readFile(path.join(outDir, "0.135.0-alpha.1", "manifest.json"), "utf8")); assert.equal(manifest.codexVersion, "0.135.0-alpha.1"); assert.equal(manifest.supports.wsTransport, true); assert.equal(manifest.supports.unixTransport, true); assert.equal(manifest.supports.threadInjectItems, true); assert.equal(manifest.supports.skillsExtraRoots, true); assert.equal(manifest.supports.hooksList, true); assert.equal(manifest.supports.threadArchive, true); assert.equal(manifest.supports.threadUnarchive, true); assert.equal(manifest.supports.threadFork, true); assert.equal(manifest.supports.threadCompactStart, true); assert.equal(manifest.supports.threadRollback, true); assert.equal(manifest.supports.threadNameSet, true); assert.equal(manifest.supports.threadMetadataUpdate, true); assert.equal(manifest.supports.threadShellCommand, true); assert.equal(manifest.supports.threadUnsubscribe, true); assert.equal(manifest.supports.turnInterrupt, true); assert.equal(manifest.supports.pluginInstall, true); assert.equal(manifest.supports.pluginUninstall, true); assert.equal(manifest.supports.pluginRead, true); assert.equal(manifest.supports.pluginSkillRead, true); assert.equal(manifest.supports.pluginShare, true); assert.equal(manifest.supports.accountLogin, true); assert.equal(manifest.supports.accountLogout, true); assert.equal(manifest.supports.accountTokenRefresh, true); assert.equal(manifest.supports.accountAddCreditsNudge, true); assert.equal(manifest.supports.configValueWrite, true); assert.equal(manifest.supports.configBatchWrite, true); assert.equal(manifest.supports.configMcpServerReload, true); assert.equal(manifest.supports.skillsConfigWrite, true); assert.equal(manifest.supports.commandExec, true); assert.equal(manifest.supports.commandExecWrite, true); assert.equal(manifest.supports.commandExecResize, true); assert.equal(manifest.supports.commandExecTerminate, true); assert.equal(manifest.supports.fsRead, true); assert.equal(manifest.supports.fsWrite, true); assert.equal(manifest.supports.fsWatch, true); assert.equal(manifest.supports.externalAgentImport, true); assert.equal(manifest.supports.marketplaceAdd, true); assert.equal(manifest.supports.marketplaceRemove, true); assert.equal(manifest.supports.marketplaceUpgrade, true); assert.equal(manifest.supports.experimentalFeatureEnablementSet, true); assert.equal(manifest.supports.reviewStart, true); assert.equal(manifest.supports.windowsSandboxReadiness, true); assert.equal(manifest.supports.windowsSandboxSetupStart, true); assert.equal(manifest.supports.fuzzyFileSearchEvents, true); assert.deepEqual(manifest.methods, [ "account/chatgptAuthTokens/refresh", "account/login/cancel", "account/login/completed", "account/login/start", "account/logout", "account/sendAddCreditsNudgeEmail", "command/exec", "command/exec/resize", "command/exec/terminate", "command/exec/write", "config/batchWrite", "config/mcpServer/reload", "config/value/write", "experimentalFeature/enablement/set", "experimentalFeature/list", "externalAgentConfig/detect", "externalAgentConfig/import", "externalAgentConfig/import/completed", "fs/copy", "fs/createDirectory", "fs/getMetadata", "fs/readDirectory", "fs/readFile", "fs/remove", "fs/unwatch", "fs/watch", "fs/writeFile", "fuzzyFileSearch/sessionCompleted", "fuzzyFileSearch/sessionUpdated", "hooks/list", "marketplace/add", "marketplace/remove", "marketplace/upgrade", "plugin/install", "plugin/read", "plugin/share/checkout", "plugin/share/delete", "plugin/share/list", "plugin/share/save", "plugin/share/updateTargets", "plugin/skill/read", "plugin/uninstall", "review/start", "skills/config/write", "skills/extraRoots/set", "thread/archive", "thread/compact/start", "thread/fork", "thread/inject_items", "thread/metadata/update", "thread/name/set", "thread/rollback", "thread/shellCommand", "thread/start", "thread/unarchive", "thread/unsubscribe", "turn/interrupt", "turn/start", "windowsSandbox/readiness", "windowsSandbox/setupCompleted", "windowsSandbox/setupStart", ]); assert.match( await readFile(path.join(outDir, "0.135.0-alpha.1", "app-server-help.txt"), "utf8"), /ws:\/\/IP:PORT/, ); } finally { await rm(runtimeRoot, { recursive: true, force: true }); } });