import test from "node:test"; import assert from "node:assert/strict"; import crypto from "node:crypto"; import { mkdtemp, rm, writeFile } from "node:fs/promises"; import http from "node:http"; import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { discoverCodexAppServerCapabilities, executeCodexAppServerTask, getCodexAppServerRunnerConfig, shouldUseCodexAppServerTaskRunner, } from "../local-agent/codex-app-server-runner.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); test("codex app-server discovery includes governance and MCP summaries without leaking internals", async () => { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, codexAppServerDiscoveryLimit: 20, codexAppServerSkillExtraRoots: [ "/Users/kris/enterprise/boss-shared-skills", "/Users/kris/enterprise/team-skills", ], }); const metadata = await discoverCodexAppServerCapabilities(runnerConfig); assert.equal(metadata.experimentalFeatures[0].name, "multi_agent"); assert.equal(metadata.experimentalFeatures[0].stage, "stable"); assert.equal(metadata.experimentalFeatures[0].enabled, true); assert.equal(metadata.collaborationModes[1].id, "plan"); assert.equal(metadata.permissionProfiles[0].id, ":workspace"); assert.equal(metadata.mcpServers[0].name, "github"); assert.equal(metadata.mcpServers[0].toolCount, 2); assert.equal(metadata.mcpServers[0].authStatus, "oAuth"); assert.deepEqual(metadata.accountSummary, { signedIn: true, authMode: "chatgpt", planType: "pro", requiresOpenaiAuth: true, }); assert.equal(metadata.rateLimitSummary.bucketCount, 2); assert.equal(metadata.rateLimitSummary.maxUsedPercent, 42); assert.equal(metadata.rateLimitSummary.reached, false); assert.deepEqual(metadata.appConfigSummary, { appCount: 2, enabledAppCount: 1, defaultEnabled: true, destructiveEnabled: false, openWorldEnabled: false, }); assert.deepEqual(metadata.configRequirements, { managed: true, requirementCount: 2, warningCount: 1, }); assert.deepEqual(metadata.externalAgentMigration, { itemCount: 3, homeItemCount: 1, projectItemCount: 2, itemTypes: ["AGENTS_MD", "MCP_SERVER_CONFIG", "SKILLS"], }); assert.deepEqual(metadata.skillExtraRootsSummary, { configured: true, status: "applied", rootCount: 2, rootLabels: ["boss-shared-skills", "team-skills"], }); assert.deepEqual(metadata.hookSummary, { workspaceCount: 1, hookCount: 2, enabledHookCount: 1, managedHookCount: 1, trustedHookCount: 1, modifiedHookCount: 1, untrustedHookCount: 0, warningCount: 1, errorCount: 1, eventNames: ["PreToolUse", "SessionStart"], handlerTypes: ["command", "prompt"], }); assert.equal(metadata.threadSummary.threadCount, 3); assert.equal(metadata.threadSummary.loadedThreadCount, 2); assert.equal(metadata.threadSummary.activeThreadCount, 1); assert.equal(metadata.threadSummary.archivedThreadCount, 1); assert.equal(metadata.threadSummary.latestUpdatedAt, "2026-06-03T08:20:00.000Z"); assert.deepEqual(metadata.threadSummary.sourceKinds, ["app", "cli"]); assert.deepEqual(metadata.threadSummary.visibleThreads[0], { id: "thr-active", name: "Boss App Server rollout", sourceKind: "app", status: "active", archived: false, loaded: true, updatedAt: "2026-06-03T08:20:00.000Z", }); assert.equal(metadata.threadTurnSummary.threadCount, 2); assert.equal(metadata.threadTurnSummary.totalTurnCount, 3); assert.equal(metadata.threadTurnSummary.runningTurnCount, 1); assert.equal(metadata.threadTurnSummary.completedTurnCount, 2); assert.equal(metadata.threadTurnSummary.latestUpdatedAt, "2026-06-03T08:21:00.000Z"); assert.deepEqual(metadata.threadTurnSummary.threads[0], { threadId: "thr-active", turnCount: 2, runningTurnCount: 1, completedTurnCount: 1, latestTurnStatus: "running", latestTurnUpdatedAt: "2026-06-03T08:21:00.000Z", }); assert.deepEqual(metadata.threadActionSummary, { actionCount: 11, lifecycleActionCount: 5, metadataActionCount: 2, liveTurnActionCount: 2, shellActionAvailable: true, userInitiatedOnly: true, labels: ["归档", "恢复", "分叉", "压缩", "回滚", "改名", "元数据", "活跃干预", "中断", "Shell", "取消订阅"], }); assert.deepEqual(metadata.pluginGovernanceSummary, { actionCount: 9, lifecycleActionCount: 2, shareActionCount: 4, readActionCount: 3, skillReadAvailable: true, userInitiatedOnly: true, labels: ["安装", "卸载", "读取", "Skill 读取", "共享保存", "共享拉取", "共享删除", "共享目标", "共享列表"], }); assert.deepEqual(metadata.accountGovernanceSummary, { actionCount: 6, loginActionCount: 3, sessionActionCount: 1, tokenRefreshAvailable: true, billingNudgeAvailable: true, userInitiatedOnly: true, labels: ["登录开始", "登录取消", "登录完成", "退出登录", "刷新令牌", "额度提醒"], }); assert.deepEqual(metadata.configGovernanceSummary, { actionCount: 5, writeActionCount: 3, reloadActionCount: 1, readActionAvailable: true, userInitiatedOnly: true, labels: ["配置读取", "单项写入", "批量写入", "MCP 重载", "Skill 配置"], }); assert.deepEqual(metadata.fileSystemGovernanceSummary, { actionCount: 9, readActionCount: 3, writeActionCount: 3, destructiveActionCount: 1, watchActionCount: 2, userInitiatedOnly: true, labels: ["读取文件", "读取目录", "元数据", "写入文件", "创建目录", "复制", "删除", "监听", "取消监听"], }); assert.deepEqual(metadata.commandSessionSummary, { actionCount: 5, controlActionCount: 3, streamAvailable: true, terminationAvailable: true, sandboxedCommandAvailable: true, userInitiatedOnly: true, labels: ["执行命令", "写入 stdin", "调整 PTY", "终止命令", "输出流"], }); assert.deepEqual(metadata.externalAgentGovernanceSummary, { actionCount: 3, importActionCount: 1, notificationActionCount: 1, detectActionAvailable: true, userInitiatedOnly: true, labels: ["迁移检测", "迁移导入", "导入完成"], }); assert.deepEqual(metadata.marketplaceGovernanceSummary, { actionCount: 3, writeActionCount: 3, upgradeAvailable: true, userInitiatedOnly: true, labels: ["添加市场", "移除市场", "升级市场"], }); assert.deepEqual(metadata.experimentalFeatureGovernanceSummary, { actionCount: 2, writeActionCount: 1, listAvailable: true, userInitiatedOnly: true, labels: ["实验列表", "启用设置"], }); assert.deepEqual(metadata.reviewGovernanceSummary, { actionCount: 1, reviewStartAvailable: true, userInitiatedOnly: true, labels: ["启动审查"], }); assert.deepEqual(metadata.windowsSandboxGovernanceSummary, { actionCount: 3, setupActionCount: 1, readinessAvailable: true, notificationAvailable: true, userInitiatedOnly: true, labels: ["准备检查", "启动设置", "设置完成"], }); assert.deepEqual(metadata.fuzzyFileSearchSummary, { eventCount: 2, completedEventAvailable: true, notificationOnly: true, labels: ["搜索更新", "搜索完成"], }); assert.deepEqual(metadata.mcpGovernanceSummary, { actionCount: 5, oauthActionCount: 2, resourceActionCount: 1, toolActionCount: 1, elicitationAvailable: true, userInitiatedOnly: true, labels: ["OAuth 登录", "OAuth 完成", "资源读取", "工具调用", "交互请求"], }); assert.deepEqual(metadata.userInteractionGovernanceSummary, { actionCount: 1, requestUserInputAvailable: true, userInitiatedOnly: true, labels: ["请求用户输入"], }); assert.deepEqual(metadata.guardianGovernanceSummary, { actionCount: 2, approvalActionCount: 1, permissionRequestEventAvailable: true, userInitiatedOnly: true, labels: ["Guardian 放行", "权限请求"], }); assert.deepEqual(metadata.runtimeEventSummary, { eventCount: 3, processEventCount: 2, rawResponseEventAvailable: true, notificationOnly: true, labels: ["进程输出", "进程退出", "原始响应完成"], }); assert.deepEqual(metadata.extensionEventSummary, { eventCount: 2, skillChangeEventAvailable: true, pluginInstallEventAvailable: true, notificationOnly: true, labels: ["Skill 变更", "插件安装"], }); assert.deepEqual(metadata.threadLifecycleEventSummary, { eventCount: 5, archiveEventCount: 2, nameEventAvailable: true, closeEventAvailable: true, notificationOnly: true, labels: ["线程启动", "线程关闭", "已归档", "已恢复", "改名完成"], }); assert.deepEqual(metadata.streamDeltaEventSummary, { eventCount: 9, reasoningDeltaEventCount: 3, commandStreamEventCount: 2, toolProgressEventAvailable: true, fileChangeOutputEventAvailable: true, notificationOnly: true, labels: ["Agent 增量", "计划增量", "思考新增", "思考文本", "原始思考", "MCP 进度", "命令输出", "终端交互", "文件输出"], }); const serialized = JSON.stringify(metadata); assert.equal(serialized.includes("sk-secret-should-not-leak"), false); assert.equal(serialized.includes("/Users/kris"), false); assert.equal(serialized.includes("id_ed25519"), false); assert.equal(serialized.includes("filesystem"), false); assert.equal(serialized.includes("resources"), false); assert.equal(serialized.includes("private-user@example.com"), false); assert.equal(serialized.includes("CLAUDE.md"), false); assert.equal(serialized.includes("AGENTS.md"), false); assert.equal(serialized.includes("/Users/kris/enterprise"), false); assert.equal(serialized.includes("secret user text should not leak"), false); assert.equal(serialized.includes("Old private thread"), false); assert.equal(serialized.includes("private active turn text should not leak"), false); assert.equal(serialized.includes("private item content should not leak"), false); assert.equal(serialized.includes("private idle turn text should not leak"), false); assert.equal(serialized.includes("private-hook.toml"), false); assert.equal(serialized.includes("private-user-hook.toml"), false); assert.equal(serialized.includes("broken-hook.toml"), false); assert.equal(serialized.includes("private hook status"), false); assert.equal(serialized.includes("private hook warning"), false); assert.equal(serialized.includes("private hook error"), false); assert.equal(serialized.includes("session-start-private-key"), false); assert.equal(serialized.includes("sk-secret-should-not-leak"), false); }); function encodeWsTextFrame(value) { const payload = Buffer.from(value); if (payload.length < 126) { return Buffer.concat([Buffer.from([0x81, payload.length]), payload]); } if (payload.length <= 0xffff) { const header = Buffer.alloc(4); header[0] = 0x81; header[1] = 126; header.writeUInt16BE(payload.length, 2); return Buffer.concat([header, payload]); } const header = Buffer.alloc(10); header[0] = 0x81; header[1] = 127; header.writeBigUInt64BE(BigInt(payload.length), 2); return Buffer.concat([header, payload]); } function decodeWsFrames(buffer, socket, onText) { let offset = 0; while (buffer.length - offset >= 2) { const first = buffer[offset]; const second = buffer[offset + 1]; const opcode = first & 0x0f; const masked = (second & 0x80) !== 0; let payloadLength = second & 0x7f; let headerLength = 2; if (payloadLength === 126) { if (buffer.length - offset < 4) break; payloadLength = buffer.readUInt16BE(offset + 2); headerLength = 4; } else if (payloadLength === 127) { if (buffer.length - offset < 10) break; payloadLength = Number(buffer.readBigUInt64BE(offset + 2)); headerLength = 10; } const maskLength = masked ? 4 : 0; const frameLength = headerLength + maskLength + payloadLength; if (buffer.length - offset < frameLength) break; let payload = buffer.subarray( offset + headerLength + maskLength, offset + frameLength, ); if (masked) { const mask = buffer.subarray(offset + headerLength, offset + headerLength + 4); const unmaskedPayload = Buffer.alloc(payload.length); for (let index = 0; index < payload.length; index += 1) { unmaskedPayload[index] = payload[index] ^ mask[index % 4]; } payload = unmaskedPayload; } if (opcode === 0x1) { onText(payload.toString("utf8")); } else if (opcode === 0x8) { socket.end(); } else if (opcode === 0x9) { socket.write(Buffer.concat([Buffer.from([0x8a, payload.length]), payload])); } offset += frameLength; } return buffer.subarray(offset); } async function createCodexAppServerWebSocketFixture(options = {}) { const server = http.createServer(); const sockets = new Set(); let lastAuthorization = ""; server.on("upgrade", (request, socket) => { sockets.add(socket); socket.on("close", () => { sockets.delete(socket); }); lastAuthorization = String(request.headers.authorization || ""); const key = request.headers["sec-websocket-key"]; const accept = crypto .createHash("sha1") .update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`) .digest("base64"); socket.write( [ "HTTP/1.1 101 Switching Protocols", "Upgrade: websocket", "Connection: Upgrade", `Sec-WebSocket-Accept: ${accept}`, "", "", ].join("\r\n"), ); let buffered = Buffer.alloc(0); const send = (message) => { socket.write(encodeWsTextFrame(JSON.stringify(message))); }; socket.on("data", (chunk) => { buffered = decodeWsFrames(Buffer.concat([buffered, chunk]), socket, (line) => { const message = JSON.parse(line); if (message.method === "initialize") { send({ id: message.id, result: { userAgent: "boss-test-codex-ws-app-server", platformFamily: "mac", platformOs: "darwin", }, }); return; } if (message.method === "initialized") { return; } if (message.method === "thread/resume") { send({ id: message.id, result: { thread: { id: message.params?.threadId, name: "ws fixture thread", }, }, }); return; } if (message.method === "turn/start") { const text = message.params?.input?.find?.((item) => item?.type === "text")?.text ?? ""; send({ id: message.id, result: { turn: { id: "ws-turn-fixture", threadId: message.params?.threadId, }, }, }); send({ method: "turn/plan/updated", params: { threadId: message.params?.threadId, turnId: "ws-turn-fixture", plan: [{ text: "通过 WebSocket 接入 Codex App Server", status: "completed" }], }, }); send({ method: "item/agentMessage/delta", params: { threadId: message.params?.threadId, turnId: "ws-turn-fixture", delta: `WS_APP_SERVER_REPLY:${text}`, }, }); send({ method: "turn/completed", params: { threadId: message.params?.threadId, turn: { id: "ws-turn-fixture", status: "completed" }, }, }); return; } send({ id: message.id, error: { code: -32601, message: `unknown method ${message.method}`, }, }); }); }); }); let socketPath; if (options.unixSocketPath) { socketPath = options.unixSocketPath; await new Promise((resolveServer) => server.listen(socketPath, resolveServer)); } else { await new Promise((resolveServer) => server.listen(0, "127.0.0.1", resolveServer)); } const address = server.address(); return { url: socketPath ? `unix://${socketPath}` : `ws://127.0.0.1:${address.port}`, getLastAuthorization: () => lastAuthorization, close: () => new Promise((resolveServer) => { for (const socket of sockets) { socket.destroy(); } server.close(resolveServer); }), }; } test("codex app-server runner resumes a thread and collects streamed agent text", async () => { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const task = { taskId: "task-app-server-1", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "继续开发并给出结果", }; assert.equal(shouldUseCodexAppServerTaskRunner(runnerConfig, task), true); const result = await executeCodexAppServerTask(runnerConfig, task); assert.equal(result.status, "completed"); assert.equal(result.threadId, "019d-app-server-thread"); assert.equal(result.cwd, repoRoot); assert.equal(result.replyBody, "APP_SERVER_REPLY:继续开发并给出结果"); assert.equal(result.transport, "stdio"); }); test("codex app-server runner converts protocol progress events into Boss execution progress", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_PROGRESS; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_PROGRESS = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-progress", taskType: "dispatch_execution", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "实现并回归", }); assert.equal(result.status, "completed"); assert.equal(result.executionProgress.steps[0].text, "读取 Codex 官方 app-server 协议"); assert.equal(result.executionProgress.steps[0].status, "done"); assert.equal(result.executionProgress.steps[1].text, "执行 targeted/full test"); assert.equal(result.executionProgress.steps[1].status, "running"); assert.equal(result.executionProgress.branch.changedFiles, 3); assert.equal(result.executionProgress.branch.additions, 181); assert.equal(result.executionProgress.branch.deletions, 52); assert.deepEqual(result.executionProgress.artifacts, [ { id: "artifact-1", label: "codex-app-server-protocol-0.135.0.json", kind: "file", }, ]); assert.deepEqual(result.executionProgress.agents, [ { name: "Mendel", role: "explorer", status: "running", }, ]); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_PROGRESS; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_PROGRESS = previous; } } }); test("codex app-server runner maps guardian approval and file-change events without leaking secrets", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_GUARDIAN_EVENTS; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_GUARDIAN_EVENTS = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-guardian", taskType: "dispatch_execution", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "需要审批和文件变更摘要", }); assert.equal(result.status, "completed"); assert.equal(result.replyBody, "APP_SERVER_REPLY:需要审批和文件变更摘要"); assert.deepEqual(result.executionProgress.approvals, [ { id: "cmd-approval-1", kind: "command", label: "命令执行审批", status: "resolved", detail: "需要确认命令执行", }, { id: "review-1", kind: "auto_review", label: "自动审批复核", status: "approved", riskLevel: "medium", }, ]); assert.deepEqual(result.executionProgress.warnings, [ { id: "guardian-warning-1", message: "检测到需要用户确认的命令执行。", severity: "warning", }, ]); assert.deepEqual(result.executionProgress.fileChanges, [ { id: "file-change-item-1-1", path: "src/app/page.tsx", kind: "update", status: "updated", }, { id: "file-change-item-1-2", path: "docs/architecture/codex_server_progress_card_cn.md", kind: "add", status: "updated", }, ]); assert.equal(result.executionProgress.branch.changedFiles, 2); assert.equal(JSON.stringify(result.executionProgress).includes("sk-secret-should-not-leak"), false); assert.equal(JSON.stringify(result.executionProgress).includes("internal prompt"), false); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_GUARDIAN_EVENTS; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_GUARDIAN_EVENTS = previous; } } }); test("codex app-server runner maps thread status and realtime events without leaking transport payloads", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_REALTIME_EVENTS; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_REALTIME_EVENTS = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-realtime", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "开启实时协作并回写状态", }); assert.equal(result.status, "completed"); assert.deepEqual(result.executionProgress.threadStatus, { type: "active", activeFlags: ["waitingOnApproval", "waitingOnUserInput"], waitingOnApproval: true, waitingOnUserInput: true, }); assert.deepEqual(result.executionProgress.realtime, { status: "closed", sessionId: "rt-session-1", version: "v2", transcriptRole: "assistant", transcriptPreview: "正在分析 Codex App Server 实时事件。", audioChunkCount: 1, itemCount: 1, closeReason: "completed", }); const serialized = JSON.stringify(result.executionProgress); assert.equal(serialized.includes("audio-secret-payload"), false); assert.equal(serialized.includes("v=0 secret"), false); assert.equal(serialized.includes("raw realtime item should not be persisted"), false); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_REALTIME_EVENTS; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_REALTIME_EVENTS = previous; } } }); test("codex app-server runner maps runtime status events without leaking internal identifiers", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_RUNTIME_STATUS; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_RUNTIME_STATUS = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-runtime-status", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "检查模型和运行状态", }); assert.equal(result.status, "completed"); assert.deepEqual(result.executionProgress.modelRoute, { fromModel: "gpt-5.4-mini", toModel: "gpt-5.4", reason: "highRiskCyberActivity", }); assert.deepEqual(result.executionProgress.tokenUsage, { totalTokens: 3000, inputTokens: 2200, cachedInputTokens: 300, outputTokens: 650, reasoningOutputTokens: 150, modelContextWindow: 200000, contextPercent: 2, }); assert.deepEqual(result.executionProgress.mcpServers, [ { name: "github", status: "failed", error: "token=[redacted] failed to start", }, ]); assert.deepEqual(result.executionProgress.remoteControl, { status: "connected", serverName: "Mac Studio", environmentId: "env-prod", }); assert.deepEqual(result.executionProgress.windowsSandbox, { status: "failed", setupMode: "elevated", error: "token=[redacted] failed in [path]", }); const serialized = JSON.stringify(result.executionProgress); assert.equal(serialized.includes("install-secret-should-not-leak"), false); assert.equal(serialized.includes("sk-secret-should-not-leak"), false); assert.equal(serialized.includes("C:\\Users\\kris\\secret"), false); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_RUNTIME_STATUS; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_RUNTIME_STATUS = previous; } } }); test("codex app-server runner maps thread goal, settings, and compaction events without leaking local paths", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_THREAD_CONFIG_EVENTS; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_THREAD_CONFIG_EVENTS = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-thread-config", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "同步线程目标和设置", }); assert.equal(result.status, "completed"); assert.deepEqual(result.executionProgress.threadGoal, { objective: "完成 App Server 线程目标同步", status: "active", tokenBudget: 120000, tokensUsed: 4800, timeUsedSeconds: 600, }); assert.deepEqual(result.executionProgress.threadSettings, { model: "gpt-5.5", modelProvider: "openai", approvalPolicy: "on-request", approvalsReviewer: "user", sandboxPolicy: "workspaceWrite", permissionProfile: ":workspace", serviceTier: "fast", effort: "low", summary: "concise", collaborationMode: "plan", personality: "pragmatic", }); assert.deepEqual(result.executionProgress.compaction, { status: "completed", message: "上下文已压缩", }); const serialized = JSON.stringify(result.executionProgress); assert.equal(serialized.includes("/Users/kris"), false); assert.equal(serialized.includes("internal prompt"), false); assert.equal(serialized.includes("secret-instructions"), false); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_THREAD_CONFIG_EVENTS; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_THREAD_CONFIG_EVENTS = previous; } } }); test("codex app-server runner maps account, quota, verification, and notices without leaking config paths", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_ACCOUNT_NOTICE_EVENTS; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_ACCOUNT_NOTICE_EVENTS = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-account-notices", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "同步账号与告警状态", }); assert.equal(result.status, "completed"); assert.deepEqual(result.executionProgress.accountStatus, { authMode: "chatgpt", planType: "team", limitId: "codex", limitName: "Codex", usedPercent: 88, windowDurationMins: 180, resetsAt: 1770003600, creditsBalance: "120.5", hasCredits: true, unlimitedCredits: false, }); assert.deepEqual(result.executionProgress.modelVerification, { verifications: ["trustedAccessForCyber"], }); assert.deepEqual(result.executionProgress.warnings, [ { id: "codex-warning-1", message: "模型切换提醒 token=[redacted]", severity: "warning", }, { id: "config-warning-2", message: "项目配置已忽略:openai_base_url 不能放在项目配置里", severity: "warning", }, { id: "deprecation-notice-3", message: "on-failure 已废弃:请改用 on-request", severity: "info", }, ]); const serialized = JSON.stringify(result.executionProgress); assert.equal(serialized.includes("/Users/kris"), false); assert.equal(serialized.includes("sk-secret-should-not-leak"), false); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_ACCOUNT_NOTICE_EVENTS; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_ACCOUNT_NOTICE_EVENTS = previous; } } }); test("codex app-server runner maps collab tool calls and context compaction without leaking thread internals", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_COLLAB_EVENTS; const previousV2 = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_COLLAB_EVENTS_V2; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_COLLAB_EVENTS = "1"; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_COLLAB_EVENTS_V2 = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-collab-events", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "让目标线程协作推进", }); assert.equal(result.status, "completed"); assert.deepEqual(result.executionProgress.threadCollaboration, { tool: "send_input", status: "completed", target: "已有线程", agentStatus: "completed · errored", receiverCount: 2, }); assert.deepEqual(result.executionProgress.compaction, { status: "completed", message: "上下文已压缩", }); const serialized = JSON.stringify(result.executionProgress); assert.equal(serialized.includes("thread-source-secret-should-not-leak"), false); assert.equal(serialized.includes("thread-target-secret-should-not-leak"), false); assert.equal(serialized.includes("thread-target-2-secret-should-not-leak"), false); assert.equal(serialized.includes("internal prompt"), false); assert.equal(serialized.includes("private agent status message"), false); assert.equal(serialized.includes("private agent completed message"), false); assert.equal(serialized.includes("private agent error message"), false); assert.equal(serialized.includes("sk-secret-should-not-leak"), false); assert.equal(serialized.includes("context-compaction-secret-should-not-leak"), false); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_COLLAB_EVENTS; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_COLLAB_EVENTS = previous; } if (previousV2 === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_COLLAB_EVENTS_V2; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_COLLAB_EVENTS_V2 = previousV2; } } }); test("codex app-server runner summarizes stream deltas without leaking raw delta content", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_STREAM_DELTA_EVENTS; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_STREAM_DELTA_EVENTS = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-stream-deltas", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "检查 Codex 流式增量进度", }); assert.equal(result.status, "completed"); assert.deepEqual(result.executionProgress.streamEvents, { status: "completed", agentDeltaCount: 1, planDeltaCount: 1, reasoningDeltaCount: 3, toolProgressCount: 1, commandOutputChunkCount: 1, terminalInteractionCount: 1, fileOutputChunkCount: 1, }); const serialized = JSON.stringify(result.executionProgress); assert.equal(serialized.includes("secret plan delta should not leak"), false); assert.equal(serialized.includes("secret reasoning summary part should not leak"), false); assert.equal(serialized.includes("secret reasoning summary delta should not leak"), false); assert.equal(serialized.includes("secret raw reasoning delta should not leak"), false); assert.equal(serialized.includes("secret mcp progress should not leak"), false); assert.equal(serialized.includes("secret command output should not leak"), false); assert.equal(serialized.includes("secret terminal input should not leak"), false); assert.equal(serialized.includes("secret file output should not leak"), false); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_STREAM_DELTA_EVENTS; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_STREAM_DELTA_EVENTS = previous; } } }); test("codex app-server runner maps tool, search, image, review, and command activities without leaking payloads", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_TOOL_ACTIVITY_EVENTS; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_TOOL_ACTIVITY_EVENTS = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-tool-activity-events", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "检查 Codex 工具活动", }); assert.equal(result.status, "completed"); assert.deepEqual(result.executionProgress.toolActivities, [ { kind: "mcp", name: "github/pull_request/list", status: "failed", detail: "token=[redacted] failed", }, { kind: "dynamic", name: "browser.open", status: "completed", detail: "成功 · 1234ms", }, { kind: "web_search", name: "openPage", status: "running", detail: "Codex App Server ThreadItem", }, { kind: "image_view", name: "imageView", status: "completed", detail: "private-screenshot.png", }, { kind: "review", name: "reviewMode", status: "exited", detail: "completed", }, { kind: "command", name: "commandExecution", status: "completed", detail: "exit 0 · 2345ms", }, ]); const serialized = JSON.stringify(result.executionProgress); assert.equal(serialized.includes("sk-secret-should-not-leak"), false); assert.equal(serialized.includes("/Users/kris"), false); assert.equal(serialized.includes("internal tool result"), false); assert.equal(serialized.includes("internal review prompt"), false); assert.equal(serialized.includes("id_rsa"), false); assert.equal(serialized.includes("https://example.com/private"), false); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_TOOL_ACTIVITY_EVENTS; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_TOOL_ACTIVITY_EVENTS = previous; } } }); test("codex app-server runner maps image generation as a safe activity and artifact", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_IMAGE_GENERATION_EVENTS; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_IMAGE_GENERATION_EVENTS = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-image-generation-events", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "检查 Codex imageGeneration item", }); assert.equal(result.status, "completed"); assert.deepEqual(result.executionProgress.toolActivities, [ { kind: "image_generation", name: "imageGeneration", status: "completed", detail: "generated-secret-image.png", }, ]); assert.deepEqual(result.executionProgress.artifacts, [ { id: "artifact-1", label: "generated-secret-image.png", kind: "image", }, ]); const serialized = JSON.stringify(result.executionProgress); assert.equal(serialized.includes("image-generation-secret-should-not-leak"), false); assert.equal(serialized.includes("raw-secret-should-not-leak"), false); assert.equal(serialized.includes("sk-secret-should-not-leak"), false); assert.equal(serialized.includes("/Users/kris"), false); assert.equal(serialized.includes("internal prompt"), false); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_IMAGE_GENERATION_EVENTS; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_IMAGE_GENERATION_EVENTS = previous; } } }); test("codex app-server runner maps hook lifecycle events without leaking hook internals", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_HOOK_EVENTS; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_HOOK_EVENTS = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-hook-events", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "检查 Codex hook 生命周期", }); assert.equal(result.status, "completed"); assert.deepEqual(result.executionProgress.toolActivities, [ { kind: "hook", name: "postToolUse/command", status: "completed", detail: "project · async · 42ms", }, ]); const serialized = JSON.stringify(result.executionProgress); assert.equal(serialized.includes("hook-secret-should-not-leak"), false); assert.equal(serialized.includes("sk-secret-should-not-leak"), false); assert.equal(serialized.includes("/Users/kris"), false); assert.equal(serialized.includes("private-hook.toml"), false); assert.equal(serialized.includes("internal hook output"), false); assert.equal(serialized.includes("private hook result"), false); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_HOOK_EVENTS; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_HOOK_EVENTS = previous; } } }); test("codex app-server runner maps item plan and reasoning summary without leaking raw reasoning", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_REASONING_PLAN_EVENTS; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_REASONING_PLAN_EVENTS = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-reasoning-plan-events", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "检查 Codex plan 和 reasoning item", }); assert.equal(result.status, "completed"); assert.deepEqual(result.executionProgress.steps, [ { id: "plan-item-1", text: "回读官方 App Server item 协议", status: "pending", }, { id: "plan-item-2", text: "补充 reasoning summary 安全映射", status: "pending", }, ]); assert.deepEqual(result.executionProgress.reasoningSummary, { status: "completed", summary: "确认只展示官方 summary,不展示 raw content。", }); const serialized = JSON.stringify(result.executionProgress); assert.equal(serialized.includes("raw hidden reasoning"), false); assert.equal(serialized.includes("raw hidden chain of thought"), false); assert.equal(serialized.includes("sk-secret-should-not-leak"), false); assert.equal(serialized.includes("reasoning-secret-should-not-leak"), false); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_REASONING_PLAN_EVENTS; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EMIT_REASONING_PLAN_EVENTS = previous; } } }); test("codex app-server runner bridges source thread context into target thread through inject_items", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_INTER_THREAD; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_INTER_THREAD = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-inter-thread", taskType: "conversation_reply", intentCategory: "thread_collaboration", sourceCodexThreadRef: "source-thread-1", sourceThreadDisplayName: "源线程", targetCodexThreadRef: "target-thread-2", targetThreadDisplayName: "目标线程", targetCodexFolderRef: repoRoot, executionPrompt: "请基于源线程结论继续实现", }); assert.equal(result.status, "completed"); assert.equal(result.threadId, "target-thread-2"); assert.equal(result.interThreadBroker.sourceThreadId, "source-thread-1"); assert.equal(result.interThreadBroker.targetThreadId, "target-thread-2"); assert.equal(result.interThreadBroker.injectedItemCount, 1); assert.match(result.replyBody, /INTER_THREAD_INJECTED/); assert.match(result.replyBody, /源线程最近结论:优先使用 app-server 协议/); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_INTER_THREAD; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_INTER_THREAD = previous; } } }); test("codex app-server runner steers an active turn when a target turn id is present", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_STEER; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_STEER = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-turn-steer", taskType: "conversation_reply", targetCodexThreadRef: "active-thread-1", targetCodexTurnId: "active-turn-1", targetCodexFolderRef: repoRoot, executionPrompt: "先暂停写入,改为只做 diff 检查", }); assert.equal(result.status, "completed"); assert.equal(result.threadId, "active-thread-1"); assert.equal(result.turnId, "active-turn-1"); assert.equal(result.turnControl, "steer"); assert.equal(result.replyBody, "STEERED:先暂停写入,改为只做 diff 检查"); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_STEER; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_STEER = previous; } } }); test("codex app-server runner interrupts the active turn when the task is canceled while running", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_WAIT_FOR_INTERRUPT; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_WAIT_FOR_INTERRUPT = "1"; try { const interruptChecks = []; const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, }); const result = await executeCodexAppServerTask( { ...runnerConfig, interruptPollIntervalMs: 10, shouldInterruptActiveTurn: async (activeTurn) => { interruptChecks.push(activeTurn); return activeTurn.taskId === "task-turn-interrupt"; }, }, { taskId: "task-turn-interrupt", taskType: "conversation_reply", targetCodexThreadRef: "active-thread-2", targetCodexFolderRef: repoRoot, executionPrompt: "继续执行一个可取消的长任务", }, ); assert.equal(result.status, "interrupted"); assert.equal(result.threadId, "active-thread-2"); assert.equal(result.turnId, "turn-fixture"); assert.equal(result.turnControl, "interrupt"); assert.equal(result.replyBody, "已按用户要求中断当前 Codex turn。"); assert.equal(interruptChecks[0]?.taskId, "task-turn-interrupt"); assert.equal(interruptChecks[0]?.threadId, "active-thread-2"); assert.equal(interruptChecks[0]?.turnId, "turn-fixture"); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_WAIT_FOR_INTERRUPT; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_WAIT_FOR_INTERRUPT = previous; } } }); test("codex app-server config exposes ws transport without mutating stdio defaults", () => { const previousTransport = process.env.BOSS_CODEX_APP_SERVER_TRANSPORT; const previousUrl = process.env.BOSS_CODEX_APP_SERVER_URL; process.env.BOSS_CODEX_APP_SERVER_TRANSPORT = "ws"; process.env.BOSS_CODEX_APP_SERVER_URL = "ws://127.0.0.1:4500"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, }); assert.equal(runnerConfig.transport, "ws"); assert.equal(runnerConfig.url, "ws://127.0.0.1:4500"); assert.equal(runnerConfig.args[0], "app-server"); } finally { if (previousTransport === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_TRANSPORT; } else { process.env.BOSS_CODEX_APP_SERVER_TRANSPORT = previousTransport; } if (previousUrl === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_URL; } else { process.env.BOSS_CODEX_APP_SERVER_URL = previousUrl; } } }); test("codex app-server runner connects to a ws app-server endpoint without spawning stdio", async () => { const fixture = await createCodexAppServerWebSocketFixture(); try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerTransport: "ws", codexAppServerUrl: fixture.url, codexAppServerCommand: "definitely-not-a-real-codex-app-server", codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, masterAgentModel: "gpt-5.4", }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-ws-app-server", taskType: "conversation_reply", targetCodexThreadRef: "ws-thread-1", targetCodexFolderRef: repoRoot, executionPrompt: "用 ws 路径回复", }); assert.equal(result.status, "completed"); assert.equal(result.transport, "ws"); assert.equal(result.threadId, "ws-thread-1"); assert.equal(result.turnId, "ws-turn-fixture"); assert.equal(result.replyBody, "WS_APP_SERVER_REPLY:用 ws 路径回复"); assert.equal(result.executionProgress.steps[0].text, "通过 WebSocket 接入 Codex App Server"); } finally { await fixture.close(); } }); test("codex app-server runner sends bearer auth during ws handshake", async () => { const fixture = await createCodexAppServerWebSocketFixture(); try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerTransport: "ws", codexAppServerUrl: fixture.url, codexAppServerAuthToken: "boss-ws-token", codexAppServerCommand: "definitely-not-a-real-codex-app-server", codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-ws-auth", taskType: "conversation_reply", targetCodexThreadRef: "ws-auth-thread", targetCodexFolderRef: repoRoot, executionPrompt: "验证 ws 鉴权", }); assert.equal(result.status, "completed"); assert.equal(result.replyBody, "WS_APP_SERVER_REPLY:验证 ws 鉴权"); assert.equal(fixture.getLastAuthorization(), "Bearer boss-ws-token"); } finally { await fixture.close(); } }); test("codex app-server runner can load bearer auth from a local token file", async () => { const runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-codex-app-server-auth-")); const tokenPath = path.join(runtimeRoot, "ws-token.txt"); await writeFile(tokenPath, "boss-token-from-file\n", "utf8"); const fixture = await createCodexAppServerWebSocketFixture(); try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerTransport: "ws", codexAppServerUrl: fixture.url, codexAppServerAuthTokenFile: tokenPath, codexAppServerCommand: "definitely-not-a-real-codex-app-server", codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-ws-auth-file", taskType: "conversation_reply", targetCodexThreadRef: "ws-auth-file-thread", targetCodexFolderRef: repoRoot, executionPrompt: "验证 token file", }); assert.equal(result.status, "completed"); assert.equal(fixture.getLastAuthorization(), "Bearer boss-token-from-file"); } finally { await fixture.close(); await rm(runtimeRoot, { recursive: true, force: true }); } }); test("codex app-server runner connects to a unix socket app-server endpoint", async () => { const runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-codex-app-server-unix-")); const fixture = await createCodexAppServerWebSocketFixture({ unixSocketPath: path.join(runtimeRoot, "codex-app-server.sock"), }); try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerTransport: "unix", codexAppServerUrl: fixture.url, codexAppServerCommand: "definitely-not-a-real-codex-app-server", codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-unix-app-server", taskType: "conversation_reply", targetCodexThreadRef: "unix-thread-1", targetCodexFolderRef: repoRoot, executionPrompt: "用 unix socket 回复", }); assert.equal(result.status, "completed"); assert.equal(result.transport, "unix"); assert.equal(result.threadId, "unix-thread-1"); assert.equal(result.replyBody, "WS_APP_SERVER_REPLY:用 unix socket 回复"); } finally { await fixture.close(); await rm(runtimeRoot, { recursive: true, force: true }); } }); test("codex app-server runner retries transient overloaded JSON-RPC requests", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_OVERLOAD_ON_TURN_START; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_OVERLOAD_ON_TURN_START = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-overloaded", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "拥塞后重试", }); assert.equal(result.status, "completed"); assert.equal(result.replyBody, "APP_SERVER_REPLY:拥塞后重试"); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_OVERLOAD_ON_TURN_START; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_OVERLOAD_ON_TURN_START = previous; } } }); test("codex app-server runner rolls back a thread without starting a new turn or leaking history", async () => { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-thread-rollback", taskType: "conversation_reply", intentCategory: "thread_rollback", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, rollbackNumTurns: 2, executionPrompt: "回滚最近 2 轮 Codex 线程历史。", }); assert.equal(result.status, "completed"); assert.equal(result.threadId, "019d-app-server-thread"); assert.equal(result.turnControl, "rollback"); assert.equal(result.rollback?.numTurns, 2); assert.match(result.replyBody, /已回滚 Codex 线程最近 2 轮/); assert.match(result.replyBody, /不会自动还原本地文件变更/); assert.doesNotMatch(result.replyBody, /private rollback turn text/); assert.equal(result.turnId, undefined); }); test("codex app-server runner stays disabled unless feature flag is explicit", () => { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], }); assert.equal( shouldUseCodexAppServerTaskRunner(runnerConfig, { taskType: "conversation_reply", targetCodexThreadRef: "thread-disabled", executionPrompt: "不会执行", }), false, ); }); test("codex app-server runner fails fast when the server exits before turn completion", async () => { const previous = process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EXIT_AFTER_TURN_START; process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EXIT_AFTER_TURN_START = "1"; try { const runnerConfig = getCodexAppServerRunnerConfig(process.env, { codexAppServerEnabled: true, codexAppServerCommand: process.execPath, codexAppServerArgs: ["tests/fixtures/codex-app-server-runtime.mjs"], codexAppServerWorkdir: repoRoot, codexAppServerTimeoutMs: 5000, }); const result = await executeCodexAppServerTask(runnerConfig, { taskId: "task-app-server-exit", taskType: "conversation_reply", targetCodexThreadRef: "019d-app-server-thread", targetCodexFolderRef: repoRoot, executionPrompt: "不要等到超时", }); assert.equal(result.status, "failed"); assert.equal(result.canFallbackToCli, false); assert.match(result.errorMessage, /CODEX_APP_SERVER_EXITED:0/); } finally { if (previous === undefined) { delete process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EXIT_AFTER_TURN_START; } else { process.env.BOSS_CODEX_APP_SERVER_FIXTURE_EXIT_AFTER_TURN_START = previous; } } });