{"repo": "ahnd6474/jakal-flow", "files": {"desktop/test/utils.test.js": {"507ad31703352251ecf52f3fb8345ddcb03fb3e8": {"t": 1785746687.3246515, "n": 84954, "h": "c3c6f58a1549bd04", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n defaultModelForProvider,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectScopedJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n providerBriefName,\n resolveProjectDirectory,\n providerCardLabel,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n providerOptionLabel,\n providerShortName,\n QWEN_CODE_DEFAULT_MODEL,\n runtimeExecutionModel,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n sameQueuedJobs,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"runtimeExecutionModel prefers execution_model over legacy runtime fields\", () => {\n assert.equal(runtimeExecutionModel({ execution_model: \"gpt-5.4\", model_slug_input: \"gpt-4.1\", model: \"gpt-4o\" }), \"gpt-5.4\");\n assert.equal(runtimeExecutionModel({ model_slug_input: \"claude-sonnet-4-6\", model: \"gpt-4o\" }), \"claude-sonnet-4-6\");\n assert.equal(runtimeExecutionModel({ model: \"gemini-3-flash-preview\" }), \"gemini-3-flash-preview\");\n assert.equal(runtimeExecutionModel({}, \"gpt-5.4\"), \"gpt-5.4\");\n});\n\ntest(\"provider label helpers keep compact, brief, option, and card names aligned\", () => {\n assert.equal(providerShortName(\"openai\"), \"Codex\");\n assert.equal(providerBriefName(\"openai\"), \"OpenAI\");\n assert.equal(providerOptionLabel(\"openai\"), \"Codex CLI\");\n assert.equal(providerCardLabel(\"openai\"), \"OpenAI\");\n\n assert.equal(providerShortName(\"oss\", \"lmstudio\"), \"OSS\");\n assert.equal(providerBriefName(\"oss\", \"lmstudio\"), \"LM Studio\");\n assert.equal(providerOptionLabel(\"oss\", \"lmstudio\"), \"LM Studio / Local OSS\");\n assert.equal(providerCardLabel(\"oss\", \"lmstudio\"), \"LM Studio / OSS\");\n\n assert.equal(providerBriefName(\"local_openai\"), \"Local OpenAI\");\n assert.equal(providerOptionLabel(\"deepseek\"), \"DeepSeek via Claude Code\");\n assert.equal(providerCardLabel(\"ensemble\"), \"Claude + GPT Ensemble\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(\n projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\", ignoreSuperseded: true })?.id,\n \"job-running\",\n );\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"projectScopedJobFromJobs centralizes lane-aware matching and queued snapshot comparison\", () => {\n const jobs = [\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n updated_at_ms: 50,\n },\n {\n id: \"job-run\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 40,\n queue_position: 2,\n },\n {\n id: \"job-other\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-2\",\n updated_at_ms: 30,\n queue_position: 1,\n },\n ];\n\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"chat\" })?.id, \"job-chat\");\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\" })?.id, \"job-run\");\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n ),\n true,\n );\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 3 }],\n ),\n false,\n );\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState prefers terminal failure over stale checkpoint running state\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n\n assert.equal(state.checkpointFamily, \"failed\");\n assert.equal(state.displayFamily, \"failed\");\n assert.equal(state.displayStatusValue, \"failed\");\n assert.equal(state.consistent, true);\n assert.match(report, /display: failed/);\n assert.doesNotMatch(report, /display: syncing/);\n});\n\ntest(\"deriveExecutionUiState lets an active rerun override stale failure snapshots\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n },\n loop_state: {\n current_checkpoint_id: \"CP1\",\n current_checkpoint_lineage_id: \"LN1\",\n pending_checkpoint_approval: false,\n },\n },\n null,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.displayStatusValue, \"running:run-plan\");\n assert.equal(state.consistent, true);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError({ reason_code: \"duplicate_job\" }), true);\n assert.equal(isDuplicateProjectJobError({ reasonCode: \"already_active_for_project\" }), true);\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), false);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), false);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n planning_mode: \"no\",\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n planning_mode: \"no\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n optimization_mode: \"off\",\n use_fast_mode: true,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n planning_mode: \"no\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n planning_mode: \"no\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"off\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), true);\n assert.equal(providerSupportsCatalog(\"local_openai\"), true);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"defaultModelForProvider prefers execution_model over stale runtime.model\", () => {\n const model = defaultModelForProvider(\"gemini\", {\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n execution_model: \"gemini-2.5-flash\",\n });\n\n assert.equal(model, \"gemini-2.5-flash\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n repo_id: \"\",\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n planning_mode: \"compact\",\n use_fast_mode: true,\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer clears stale planning progress for reopened projects\", () => {\n const nowMs = Date.parse(\"2026-04-01T00:00:00Z\");\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-27T10:00:05Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null, { nowMs });\n\n assert.equal(sanitizedDetail.project.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.planning_progress, null);\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n repo_id: \"\",\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildProjectPayload preserves runtime fields that must reach the backend bridge\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \"\",\n github_mode: \"existing\",\n runtime: {\n execution_model: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n },\n });\n\n assert.deepEqual(payload.runtime, {\n execution_model: \"gpt-5.4-mini\",\n model: \"gpt-5.4-mini\",\n model_slug_input: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n });\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n repo_id: \"\",\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n planning_mode: \"compact\",\n use_fast_mode: true,\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", planning_mode: \"compact\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | planning compact\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"program settings normalize planning mode from legacy fast flag and apply it to project runtime\", () => {\n const settings = programSettingsFromRuntime({\n use_fast_mode: true,\n });\n\n assert.equal(settings.planning_mode, \"compact\");\n assert.equal(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ).planning_mode,\n \"compact\",\n );\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep allows pending, failed, and paused steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"failed\" }, false), true);\n assert.equal(canEditStep({ status: \"paused\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n assert.equal(effectiveStepStatus({ status: \"failed\" }, \"running:run-plan\"), \"running:run-plan\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "26f26535a82576a3e66b8d54375df0aa6c0af63d": {"t": 1785766846.4933858, "n": 84954, "h": "c3c6f58a1549bd04", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n defaultModelForProvider,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectScopedJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n providerBriefName,\n resolveProjectDirectory,\n providerCardLabel,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n providerOptionLabel,\n providerShortName,\n QWEN_CODE_DEFAULT_MODEL,\n runtimeExecutionModel,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n sameQueuedJobs,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"runtimeExecutionModel prefers execution_model over legacy runtime fields\", () => {\n assert.equal(runtimeExecutionModel({ execution_model: \"gpt-5.4\", model_slug_input: \"gpt-4.1\", model: \"gpt-4o\" }), \"gpt-5.4\");\n assert.equal(runtimeExecutionModel({ model_slug_input: \"claude-sonnet-4-6\", model: \"gpt-4o\" }), \"claude-sonnet-4-6\");\n assert.equal(runtimeExecutionModel({ model: \"gemini-3-flash-preview\" }), \"gemini-3-flash-preview\");\n assert.equal(runtimeExecutionModel({}, \"gpt-5.4\"), \"gpt-5.4\");\n});\n\ntest(\"provider label helpers keep compact, brief, option, and card names aligned\", () => {\n assert.equal(providerShortName(\"openai\"), \"Codex\");\n assert.equal(providerBriefName(\"openai\"), \"OpenAI\");\n assert.equal(providerOptionLabel(\"openai\"), \"Codex CLI\");\n assert.equal(providerCardLabel(\"openai\"), \"OpenAI\");\n\n assert.equal(providerShortName(\"oss\", \"lmstudio\"), \"OSS\");\n assert.equal(providerBriefName(\"oss\", \"lmstudio\"), \"LM Studio\");\n assert.equal(providerOptionLabel(\"oss\", \"lmstudio\"), \"LM Studio / Local OSS\");\n assert.equal(providerCardLabel(\"oss\", \"lmstudio\"), \"LM Studio / OSS\");\n\n assert.equal(providerBriefName(\"local_openai\"), \"Local OpenAI\");\n assert.equal(providerOptionLabel(\"deepseek\"), \"DeepSeek via Claude Code\");\n assert.equal(providerCardLabel(\"ensemble\"), \"Claude + GPT Ensemble\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(\n projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\", ignoreSuperseded: true })?.id,\n \"job-running\",\n );\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"projectScopedJobFromJobs centralizes lane-aware matching and queued snapshot comparison\", () => {\n const jobs = [\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n updated_at_ms: 50,\n },\n {\n id: \"job-run\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 40,\n queue_position: 2,\n },\n {\n id: \"job-other\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-2\",\n updated_at_ms: 30,\n queue_position: 1,\n },\n ];\n\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"chat\" })?.id, \"job-chat\");\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\" })?.id, \"job-run\");\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n ),\n true,\n );\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 3 }],\n ),\n false,\n );\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState prefers terminal failure over stale checkpoint running state\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n\n assert.equal(state.checkpointFamily, \"failed\");\n assert.equal(state.displayFamily, \"failed\");\n assert.equal(state.displayStatusValue, \"failed\");\n assert.equal(state.consistent, true);\n assert.match(report, /display: failed/);\n assert.doesNotMatch(report, /display: syncing/);\n});\n\ntest(\"deriveExecutionUiState lets an active rerun override stale failure snapshots\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n },\n loop_state: {\n current_checkpoint_id: \"CP1\",\n current_checkpoint_lineage_id: \"LN1\",\n pending_checkpoint_approval: false,\n },\n },\n null,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.displayStatusValue, \"running:run-plan\");\n assert.equal(state.consistent, true);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError({ reason_code: \"duplicate_job\" }), true);\n assert.equal(isDuplicateProjectJobError({ reasonCode: \"already_active_for_project\" }), true);\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), false);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), false);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n planning_mode: \"no\",\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n planning_mode: \"no\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n optimization_mode: \"off\",\n use_fast_mode: true,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n planning_mode: \"no\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n planning_mode: \"no\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"off\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), true);\n assert.equal(providerSupportsCatalog(\"local_openai\"), true);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"defaultModelForProvider prefers execution_model over stale runtime.model\", () => {\n const model = defaultModelForProvider(\"gemini\", {\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n execution_model: \"gemini-2.5-flash\",\n });\n\n assert.equal(model, \"gemini-2.5-flash\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n repo_id: \"\",\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n planning_mode: \"compact\",\n use_fast_mode: true,\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer clears stale planning progress for reopened projects\", () => {\n const nowMs = Date.parse(\"2026-04-01T00:00:00Z\");\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-27T10:00:05Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null, { nowMs });\n\n assert.equal(sanitizedDetail.project.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.planning_progress, null);\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n repo_id: \"\",\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildProjectPayload preserves runtime fields that must reach the backend bridge\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \"\",\n github_mode: \"existing\",\n runtime: {\n execution_model: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n },\n });\n\n assert.deepEqual(payload.runtime, {\n execution_model: \"gpt-5.4-mini\",\n model: \"gpt-5.4-mini\",\n model_slug_input: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n });\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n repo_id: \"\",\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n planning_mode: \"compact\",\n use_fast_mode: true,\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", planning_mode: \"compact\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | planning compact\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"program settings normalize planning mode from legacy fast flag and apply it to project runtime\", () => {\n const settings = programSettingsFromRuntime({\n use_fast_mode: true,\n });\n\n assert.equal(settings.planning_mode, \"compact\");\n assert.equal(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ).planning_mode,\n \"compact\",\n );\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep allows pending, failed, and paused steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"failed\" }, false), true);\n assert.equal(canEditStep({ status: \"paused\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n assert.equal(effectiveStepStatus({ status: \"failed\" }, \"running:run-plan\"), \"running:run-plan\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "7a88618a81f9724fa11fcecfc38b4adb18f588f1": {"t": 1785766848.0754611, "n": 84254, "h": "22e7e93c007dad7f", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n defaultModelForProvider,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectScopedJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n providerBriefName,\n resolveProjectDirectory,\n providerCardLabel,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n providerOptionLabel,\n providerShortName,\n QWEN_CODE_DEFAULT_MODEL,\n runtimeExecutionModel,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n sameQueuedJobs,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"runtimeExecutionModel prefers execution_model over legacy runtime fields\", () => {\n assert.equal(runtimeExecutionModel({ execution_model: \"gpt-5.4\", model_slug_input: \"gpt-4.1\", model: \"gpt-4o\" }), \"gpt-5.4\");\n assert.equal(runtimeExecutionModel({ model_slug_input: \"claude-sonnet-4-6\", model: \"gpt-4o\" }), \"claude-sonnet-4-6\");\n assert.equal(runtimeExecutionModel({ model: \"gemini-3-flash-preview\" }), \"gemini-3-flash-preview\");\n assert.equal(runtimeExecutionModel({}, \"gpt-5.4\"), \"gpt-5.4\");\n});\n\ntest(\"provider label helpers keep compact, brief, option, and card names aligned\", () => {\n assert.equal(providerShortName(\"openai\"), \"Codex\");\n assert.equal(providerBriefName(\"openai\"), \"OpenAI\");\n assert.equal(providerOptionLabel(\"openai\"), \"Codex CLI\");\n assert.equal(providerCardLabel(\"openai\"), \"OpenAI\");\n\n assert.equal(providerShortName(\"oss\", \"lmstudio\"), \"OSS\");\n assert.equal(providerBriefName(\"oss\", \"lmstudio\"), \"LM Studio\");\n assert.equal(providerOptionLabel(\"oss\", \"lmstudio\"), \"LM Studio / Local OSS\");\n assert.equal(providerCardLabel(\"oss\", \"lmstudio\"), \"LM Studio / OSS\");\n\n assert.equal(providerBriefName(\"local_openai\"), \"Local OpenAI\");\n assert.equal(providerOptionLabel(\"deepseek\"), \"DeepSeek via Claude Code\");\n assert.equal(providerCardLabel(\"ensemble\"), \"Claude + GPT Ensemble\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(\n projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\", ignoreSuperseded: true })?.id,\n \"job-running\",\n );\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"projectScopedJobFromJobs centralizes lane-aware matching and queued snapshot comparison\", () => {\n const jobs = [\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n updated_at_ms: 50,\n },\n {\n id: \"job-run\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 40,\n queue_position: 2,\n },\n {\n id: \"job-other\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-2\",\n updated_at_ms: 30,\n queue_position: 1,\n },\n ];\n\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"chat\" })?.id, \"job-chat\");\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\" })?.id, \"job-run\");\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n ),\n true,\n );\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 3 }],\n ),\n false,\n );\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState prefers terminal failure over stale checkpoint running state\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n\n assert.equal(state.checkpointFamily, \"failed\");\n assert.equal(state.displayFamily, \"failed\");\n assert.equal(state.displayStatusValue, \"failed\");\n assert.equal(state.consistent, true);\n assert.match(report, /display: failed/);\n assert.doesNotMatch(report, /display: syncing/);\n});\n\ntest(\"deriveExecutionUiState lets an active rerun override stale failure snapshots\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n },\n loop_state: {\n current_checkpoint_id: \"CP1\",\n current_checkpoint_lineage_id: \"LN1\",\n pending_checkpoint_approval: false,\n },\n },\n null,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.displayStatusValue, \"running:run-plan\");\n assert.equal(state.consistent, true);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError({ reason_code: \"duplicate_job\" }), true);\n assert.equal(isDuplicateProjectJobError({ reasonCode: \"already_active_for_project\" }), true);\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), false);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), false);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n optimization_mode: \"off\",\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"off\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), true);\n assert.equal(providerSupportsCatalog(\"local_openai\"), true);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"defaultModelForProvider prefers execution_model over stale runtime.model\", () => {\n const model = defaultModelForProvider(\"gemini\", {\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n execution_model: \"gemini-2.5-flash\",\n });\n\n assert.equal(model, \"gemini-2.5-flash\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer clears stale planning progress for reopened projects\", () => {\n const nowMs = Date.parse(\"2026-04-01T00:00:00Z\");\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-27T10:00:05Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null, { nowMs });\n\n assert.equal(sanitizedDetail.project.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.planning_progress, null);\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildProjectPayload preserves runtime fields that must reach the backend bridge\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \"\",\n github_mode: \"existing\",\n runtime: {\n execution_model: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n },\n });\n\n assert.deepEqual(payload.runtime, {\n execution_model: \"gpt-5.4-mini\",\n model: \"gpt-5.4-mini\",\n model_slug_input: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n });\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep allows pending, failed, and paused steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"failed\" }, false), true);\n assert.equal(canEditStep({ status: \"paused\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n assert.equal(effectiveStepStatus({ status: \"failed\" }, \"running:run-plan\"), \"running:run-plan\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "df14de0db908c10d93eb57a1a4643bc53b3f6205": {"t": 1785766851.7923415, "n": 82816, "h": "0afc424cec483162", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n defaultModelForProvider,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectScopedJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n sameQueuedJobs,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(\n projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\", ignoreSuperseded: true })?.id,\n \"job-running\",\n );\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"projectScopedJobFromJobs centralizes lane-aware matching and queued snapshot comparison\", () => {\n const jobs = [\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n updated_at_ms: 50,\n },\n {\n id: \"job-run\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 40,\n queue_position: 2,\n },\n {\n id: \"job-other\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-2\",\n updated_at_ms: 30,\n queue_position: 1,\n },\n ];\n\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"chat\" })?.id, \"job-chat\");\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\" })?.id, \"job-run\");\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n ),\n true,\n );\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 3 }],\n ),\n false,\n );\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState prefers terminal failure over stale checkpoint running state\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n\n assert.equal(state.checkpointFamily, \"failed\");\n assert.equal(state.displayFamily, \"failed\");\n assert.equal(state.displayStatusValue, \"failed\");\n assert.equal(state.consistent, true);\n assert.match(report, /display: failed/);\n assert.doesNotMatch(report, /display: syncing/);\n});\n\ntest(\"deriveExecutionUiState lets an active rerun override stale failure snapshots\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n },\n loop_state: {\n current_checkpoint_id: \"CP1\",\n current_checkpoint_lineage_id: \"LN1\",\n pending_checkpoint_approval: false,\n },\n },\n null,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.displayStatusValue, \"running:run-plan\");\n assert.equal(state.consistent, true);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError({ reason_code: \"duplicate_job\" }), true);\n assert.equal(isDuplicateProjectJobError({ reasonCode: \"already_active_for_project\" }), true);\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), false);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), false);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n optimization_mode: \"off\",\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"off\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"defaultModelForProvider prefers execution_model over stale runtime.model\", () => {\n const model = defaultModelForProvider(\"gemini\", {\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n execution_model: \"gemini-2.5-flash\",\n });\n\n assert.equal(model, \"gemini-2.5-flash\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer clears stale planning progress for reopened projects\", () => {\n const nowMs = Date.parse(\"2026-04-01T00:00:00Z\");\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-27T10:00:05Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null, { nowMs });\n\n assert.equal(sanitizedDetail.project.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.planning_progress, null);\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildProjectPayload preserves runtime fields that must reach the backend bridge\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \"\",\n github_mode: \"existing\",\n runtime: {\n execution_model: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n },\n });\n\n assert.deepEqual(payload.runtime, {\n execution_model: \"gpt-5.4-mini\",\n model: \"gpt-5.4-mini\",\n model_slug_input: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n });\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep allows pending, failed, and paused steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"failed\" }, false), true);\n assert.equal(canEditStep({ status: \"paused\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n assert.equal(effectiveStepStatus({ status: \"failed\" }, \"running:run-plan\"), \"running:run-plan\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "b7014858a573d97ca5117beadbc788265eaa3585": {"t": 1785766854.0879757, "n": 82634, "h": "65f55c939c1c67c7", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n defaultModelForProvider,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectScopedJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n sameQueuedJobs,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(\n projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\", ignoreSuperseded: true })?.id,\n \"job-running\",\n );\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"projectScopedJobFromJobs centralizes lane-aware matching and queued snapshot comparison\", () => {\n const jobs = [\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n updated_at_ms: 50,\n },\n {\n id: \"job-run\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 40,\n queue_position: 2,\n },\n {\n id: \"job-other\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-2\",\n updated_at_ms: 30,\n queue_position: 1,\n },\n ];\n\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"chat\" })?.id, \"job-chat\");\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\" })?.id, \"job-run\");\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n ),\n true,\n );\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 3 }],\n ),\n false,\n );\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState prefers terminal failure over stale checkpoint running state\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n\n assert.equal(state.checkpointFamily, \"failed\");\n assert.equal(state.displayFamily, \"failed\");\n assert.equal(state.displayStatusValue, \"failed\");\n assert.equal(state.consistent, true);\n assert.match(report, /display: failed/);\n assert.doesNotMatch(report, /display: syncing/);\n});\n\ntest(\"deriveExecutionUiState lets an active rerun override stale failure snapshots\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n },\n loop_state: {\n current_checkpoint_id: \"CP1\",\n current_checkpoint_lineage_id: \"LN1\",\n pending_checkpoint_approval: false,\n },\n },\n null,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.displayStatusValue, \"running:run-plan\");\n assert.equal(state.consistent, true);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n optimization_mode: \"off\",\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"off\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"defaultModelForProvider prefers execution_model over stale runtime.model\", () => {\n const model = defaultModelForProvider(\"gemini\", {\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n execution_model: \"gemini-2.5-flash\",\n });\n\n assert.equal(model, \"gemini-2.5-flash\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer clears stale planning progress for reopened projects\", () => {\n const nowMs = Date.parse(\"2026-04-01T00:00:00Z\");\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-27T10:00:05Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null, { nowMs });\n\n assert.equal(sanitizedDetail.project.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.planning_progress, null);\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildProjectPayload preserves runtime fields that must reach the backend bridge\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \"\",\n github_mode: \"existing\",\n runtime: {\n execution_model: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n },\n });\n\n assert.deepEqual(payload.runtime, {\n execution_model: \"gpt-5.4-mini\",\n model: \"gpt-5.4-mini\",\n model_slug_input: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n });\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep allows pending, failed, and paused steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"failed\" }, false), true);\n assert.equal(canEditStep({ status: \"paused\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n assert.equal(effectiveStepStatus({ status: \"failed\" }, \"running:run-plan\"), \"running:run-plan\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "2954c99fb35e6a9c93d29324cfb0603e93bb485b": {"t": 1785766855.8130295, "n": 82606, "h": "27de68f4eb8c64fd", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n defaultModelForProvider,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectScopedJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n sameQueuedJobs,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(\n projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\", ignoreSuperseded: true })?.id,\n \"job-running\",\n );\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"projectScopedJobFromJobs centralizes lane-aware matching and queued snapshot comparison\", () => {\n const jobs = [\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n updated_at_ms: 50,\n },\n {\n id: \"job-run\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 40,\n queue_position: 2,\n },\n {\n id: \"job-other\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-2\",\n updated_at_ms: 30,\n queue_position: 1,\n },\n ];\n\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"chat\" })?.id, \"job-chat\");\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\" })?.id, \"job-run\");\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n ),\n true,\n );\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 3 }],\n ),\n false,\n );\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState prefers terminal failure over stale checkpoint running state\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n\n assert.equal(state.checkpointFamily, \"failed\");\n assert.equal(state.displayFamily, \"failed\");\n assert.equal(state.displayStatusValue, \"failed\");\n assert.equal(state.consistent, true);\n assert.match(report, /display: failed/);\n assert.doesNotMatch(report, /display: syncing/);\n});\n\ntest(\"deriveExecutionUiState lets an active rerun override stale failure snapshots\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n },\n loop_state: {\n current_checkpoint_id: \"CP1\",\n current_checkpoint_lineage_id: \"LN1\",\n pending_checkpoint_approval: false,\n },\n },\n null,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.displayStatusValue, \"running:run-plan\");\n assert.equal(state.consistent, true);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"defaultModelForProvider prefers execution_model over stale runtime.model\", () => {\n const model = defaultModelForProvider(\"gemini\", {\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n execution_model: \"gemini-2.5-flash\",\n });\n\n assert.equal(model, \"gemini-2.5-flash\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer clears stale planning progress for reopened projects\", () => {\n const nowMs = Date.parse(\"2026-04-01T00:00:00Z\");\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-27T10:00:05Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null, { nowMs });\n\n assert.equal(sanitizedDetail.project.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.planning_progress, null);\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildProjectPayload preserves runtime fields that must reach the backend bridge\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \"\",\n github_mode: \"existing\",\n runtime: {\n execution_model: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n },\n });\n\n assert.deepEqual(payload.runtime, {\n execution_model: \"gpt-5.4-mini\",\n model: \"gpt-5.4-mini\",\n model_slug_input: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n });\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep allows pending, failed, and paused steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"failed\" }, false), true);\n assert.equal(canEditStep({ status: \"paused\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n assert.equal(effectiveStepStatus({ status: \"failed\" }, \"running:run-plan\"), \"running:run-plan\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "1b2e3f0a64f6a4e09169d8b7c9840e82e412a11b": {"t": 1785766857.755884, "n": 82604, "h": "c0541c864b8a2231", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n defaultModelForProvider,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectScopedJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n sameQueuedJobs,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(\n projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\", ignoreSuperseded: true })?.id,\n \"job-running\",\n );\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"projectScopedJobFromJobs centralizes lane-aware matching and queued snapshot comparison\", () => {\n const jobs = [\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n updated_at_ms: 50,\n },\n {\n id: \"job-run\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 40,\n queue_position: 2,\n },\n {\n id: \"job-other\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-2\",\n updated_at_ms: 30,\n queue_position: 1,\n },\n ];\n\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"chat\" })?.id, \"job-chat\");\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\" })?.id, \"job-run\");\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n ),\n true,\n );\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 3 }],\n ),\n false,\n );\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState prefers terminal failure over stale checkpoint running state\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n\n assert.equal(state.checkpointFamily, \"failed\");\n assert.equal(state.displayFamily, \"failed\");\n assert.equal(state.displayStatusValue, \"failed\");\n assert.equal(state.consistent, true);\n assert.match(report, /display: failed/);\n assert.doesNotMatch(report, /display: syncing/);\n});\n\ntest(\"deriveExecutionUiState lets an active rerun override stale failure snapshots\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n },\n loop_state: {\n current_checkpoint_id: \"CP1\",\n current_checkpoint_lineage_id: \"LN1\",\n pending_checkpoint_approval: false,\n },\n },\n null,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.displayStatusValue, \"running:run-plan\");\n assert.equal(state.consistent, true);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"defaultModelForProvider prefers execution_model over stale runtime.model\", () => {\n const model = defaultModelForProvider(\"gemini\", {\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n execution_model: \"gemini-2.5-flash\",\n });\n\n assert.equal(model, \"gemini-2.5-flash\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer clears stale planning progress for reopened projects\", () => {\n const nowMs = Date.parse(\"2026-04-01T00:00:00Z\");\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-27T10:00:05Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null, { nowMs });\n\n assert.equal(sanitizedDetail.project.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"setup_ready\");\n assert.equal(sanitizedDetail.planning_progress, null);\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildProjectPayload preserves runtime fields that must reach the backend bridge\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \"\",\n github_mode: \"existing\",\n runtime: {\n execution_model: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n },\n });\n\n assert.deepEqual(payload.runtime, {\n execution_model: \"gpt-5.4-mini\",\n model: \"gpt-5.4-mini\",\n model_slug_input: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n });\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep allows pending, failed, and paused steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"failed\" }, false), true);\n assert.equal(canEditStep({ status: \"paused\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n assert.equal(effectiveStepStatus({ status: \"failed\" }, \"running:run-plan\"), \"running:run-plan\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "ee2d8b827fecb9ac3d43fb4ab5efb2e0ae12bf21": {"t": 1785766860.2607338, "n": 81671, "h": "182a5818868a46c8", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n defaultModelForProvider,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectScopedJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n sameQueuedJobs,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(\n projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\", ignoreSuperseded: true })?.id,\n \"job-running\",\n );\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"projectScopedJobFromJobs centralizes lane-aware matching and queued snapshot comparison\", () => {\n const jobs = [\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n updated_at_ms: 50,\n },\n {\n id: \"job-run\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 40,\n queue_position: 2,\n },\n {\n id: \"job-other\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-2\",\n updated_at_ms: 30,\n queue_position: 1,\n },\n ];\n\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"chat\" })?.id, \"job-chat\");\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\" })?.id, \"job-run\");\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n ),\n true,\n );\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 3 }],\n ),\n false,\n );\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState prefers terminal failure over stale checkpoint running state\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n\n assert.equal(state.checkpointFamily, \"failed\");\n assert.equal(state.displayFamily, \"failed\");\n assert.equal(state.displayStatusValue, \"failed\");\n assert.equal(state.consistent, true);\n assert.match(report, /display: failed/);\n assert.doesNotMatch(report, /display: syncing/);\n});\n\ntest(\"deriveExecutionUiState lets an active rerun override stale failure snapshots\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n },\n loop_state: {\n current_checkpoint_id: \"CP1\",\n current_checkpoint_lineage_id: \"LN1\",\n pending_checkpoint_approval: false,\n },\n },\n null,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.displayStatusValue, \"running:run-plan\");\n assert.equal(state.consistent, true);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"defaultModelForProvider prefers execution_model over stale runtime.model\", () => {\n const model = defaultModelForProvider(\"gemini\", {\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n execution_model: \"gemini-2.5-flash\",\n });\n\n assert.equal(model, \"gemini-2.5-flash\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildProjectPayload preserves runtime fields that must reach the backend bridge\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \"\",\n github_mode: \"existing\",\n runtime: {\n execution_model: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n },\n });\n\n assert.deepEqual(payload.runtime, {\n execution_model: \"gpt-5.4-mini\",\n model: \"gpt-5.4-mini\",\n model_slug_input: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n });\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep allows pending, failed, and paused steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"failed\" }, false), true);\n assert.equal(canEditStep({ status: \"paused\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n assert.equal(effectiveStepStatus({ status: \"failed\" }, \"running:run-plan\"), \"running:run-plan\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "534aca43c42d03d2abca40879a6a78995471f8ee": {"t": 1785766885.4604607, "n": 81177, "h": "4529aa6938186aa3", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectScopedJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n sameQueuedJobs,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(\n projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\", ignoreSuperseded: true })?.id,\n \"job-running\",\n );\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"projectScopedJobFromJobs centralizes lane-aware matching and queued snapshot comparison\", () => {\n const jobs = [\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n updated_at_ms: 50,\n },\n {\n id: \"job-run\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 40,\n queue_position: 2,\n },\n {\n id: \"job-other\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-2\",\n updated_at_ms: 30,\n queue_position: 1,\n },\n ];\n\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"chat\" })?.id, \"job-chat\");\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\" })?.id, \"job-run\");\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n ),\n true,\n );\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 3 }],\n ),\n false,\n );\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState prefers terminal failure over stale checkpoint running state\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n\n assert.equal(state.checkpointFamily, \"failed\");\n assert.equal(state.displayFamily, \"failed\");\n assert.equal(state.displayStatusValue, \"failed\");\n assert.equal(state.consistent, true);\n assert.match(report, /display: failed/);\n assert.doesNotMatch(report, /display: syncing/);\n});\n\ntest(\"deriveExecutionUiState lets an active rerun override stale failure snapshots\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n },\n loop_state: {\n current_checkpoint_id: \"CP1\",\n current_checkpoint_lineage_id: \"LN1\",\n pending_checkpoint_approval: false,\n },\n },\n null,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.displayStatusValue, \"running:run-plan\");\n assert.equal(state.consistent, true);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildProjectPayload preserves runtime fields that must reach the backend bridge\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \"\",\n github_mode: \"existing\",\n runtime: {\n execution_model: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n },\n });\n\n assert.deepEqual(payload.runtime, {\n execution_model: \"gpt-5.4-mini\",\n allow_background_queue: false,\n require_checkpoint_approval: false,\n checkpoint_interval_blocks: 3,\n parallel_memory_per_worker_gib: 4.5,\n local_model_provider: \"lmstudio\",\n chat_model_provider: \"oss\",\n generate_word_report: true,\n });\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep allows pending, failed, and paused steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"failed\" }, false), true);\n assert.equal(canEditStep({ status: \"paused\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n assert.equal(effectiveStepStatus({ status: \"failed\" }, \"running:run-plan\"), \"running:run-plan\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "49be6b3a9fca109b438e04e40f55a6be0d599f43": {"t": 1785766888.0378568, "n": 80247, "h": "2f1f7f088079e94a", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectScopedJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n sameQueuedJobs,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(\n projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\", ignoreSuperseded: true })?.id,\n \"job-running\",\n );\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"projectScopedJobFromJobs centralizes lane-aware matching and queued snapshot comparison\", () => {\n const jobs = [\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n updated_at_ms: 50,\n },\n {\n id: \"job-run\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 40,\n queue_position: 2,\n },\n {\n id: \"job-other\",\n status: \"queued\",\n command: \"run-plan\",\n repo_id: \"repo-2\",\n updated_at_ms: 30,\n queue_position: 1,\n },\n ];\n\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"chat\" })?.id, \"job-chat\");\n assert.equal(projectScopedJobFromJobs(jobs, { repo_id: \"repo-1\" }, { lane: \"execution\" })?.id, \"job-run\");\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n ),\n true,\n );\n assert.equal(\n sameQueuedJobs(\n [{ id: \"job-run\", status: \"queued\", queue_position: 2 }],\n [{ id: \"job-run\", status: \"queued\", queue_position: 3 }],\n ),\n false,\n );\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState prefers terminal failure over stale checkpoint running state\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n\n assert.equal(state.checkpointFamily, \"failed\");\n assert.equal(state.displayFamily, \"failed\");\n assert.equal(state.displayStatusValue, \"failed\");\n assert.equal(state.consistent, true);\n assert.match(report, /display: failed/);\n assert.doesNotMatch(report, /display: syncing/);\n});\n\ntest(\"deriveExecutionUiState lets an active rerun override stale failure snapshots\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n },\n loop_state: {\n current_checkpoint_id: \"CP1\",\n current_checkpoint_lineage_id: \"LN1\",\n pending_checkpoint_approval: false,\n },\n },\n null,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.displayStatusValue, \"running:run-plan\");\n assert.equal(state.consistent, true);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep allows pending, failed, and paused steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"failed\" }, false), true);\n assert.equal(canEditStep({ status: \"paused\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n assert.equal(effectiveStepStatus({ status: \"failed\" }, \"running:run-plan\"), \"running:run-plan\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "b784ab051c7db456114ef05013730fef0450faae": {"t": 1785766889.7050815, "n": 78864, "h": "a8d90c449f10a965", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState prefers terminal failure over stale checkpoint running state\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n\n assert.equal(state.checkpointFamily, \"failed\");\n assert.equal(state.displayFamily, \"failed\");\n assert.equal(state.displayStatusValue, \"failed\");\n assert.equal(state.consistent, true);\n assert.match(report, /display: failed/);\n assert.doesNotMatch(report, /display: syncing/);\n});\n\ntest(\"deriveExecutionUiState lets an active rerun override stale failure snapshots\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n },\n loop_state: {\n current_checkpoint_id: \"CP1\",\n current_checkpoint_lineage_id: \"LN1\",\n pending_checkpoint_approval: false,\n },\n },\n null,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.displayStatusValue, \"running:run-plan\");\n assert.equal(state.consistent, true);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep allows pending, failed, and paused steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"failed\" }, false), true);\n assert.equal(canEditStep({ status: \"paused\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n assert.equal(effectiveStepStatus({ status: \"failed\" }, \"running:run-plan\"), \"running:run-plan\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "e54f4ef86435693b9c6a966b32c61e23a3612fab": {"t": 1785766891.8814285, "n": 78721, "h": "84d1d40bf3dd2519", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState prefers terminal failure over stale checkpoint running state\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n null,\n null,\n );\n\n assert.equal(state.checkpointFamily, \"failed\");\n assert.equal(state.displayFamily, \"failed\");\n assert.equal(state.displayStatusValue, \"failed\");\n assert.equal(state.consistent, true);\n assert.match(report, /display: failed/);\n assert.doesNotMatch(report, /display: syncing/);\n});\n\ntest(\"deriveExecutionUiState lets an active rerun override stale failure snapshots\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"failed\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"failed\",\n },\n ],\n },\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n lineage_id: \"LN1\",\n status: \"pending\",\n title: \"Review build\",\n },\n },\n loop_state: {\n current_checkpoint_id: \"CP1\",\n current_checkpoint_lineage_id: \"LN1\",\n pending_checkpoint_approval: false,\n },\n },\n null,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.displayStatusValue, \"running:run-plan\");\n assert.equal(state.consistent, true);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n assert.equal(effectiveStepStatus({ status: \"failed\" }, \"running:run-plan\"), \"running:run-plan\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "436ffce969893f10ad96adea15e4a1cf37c5180a": {"t": 1785766893.6195054, "n": 75493, "h": "bc702436e4ab4029", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:debugging\",\n },\n plan,\n checkpoints: {\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n pending: {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: debugging/);\n assert.match(report, /flow: debugging/);\n assert.match(report, /checkpoint: running/);\n assert.match(report, /process: debugging/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-manual-debugger\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "626fd5618a5ca2c7a15971aaa88f08488965c270": {"t": 1785766895.3991334, "n": 75493, "h": "a7f0fdd19f36979f", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n stepModelSelectionPatch,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n ],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n ],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: checkpoint/);\n assert.match(report, /flow: checkpoint/);\n assert.match(report, /checkpoint: checkpoint/);\n assert.match(report, /process: running/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_title: \"Custom Closeout\",\n closeout_display_description: \"Wrap up the project\",\n closeout_codex_description: \"Wrap up the project\",\n closeout_success_criteria: \"Confirm the repo is ready to ship\",\n closeout_reasoning_effort: \"xhigh\",\n closeout_model_provider: \"claude\",\n closeout_model: \"claude-sonnet-4-6\",\n closeout_parallel_group: \"final\",\n closeout_depends_on: [\"ST2\"],\n closeout_owned_paths: [\"README.md\"],\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].title, \"Custom Closeout\");\n assert.equal(steps[2].display_description, \"Wrap up the project\");\n assert.equal(steps[2].codex_description, \"Wrap up the project\");\n assert.equal(steps[2].success_criteria, \"Confirm the repo is ready to ship\");\n assert.equal(steps[2].reasoning_effort, \"xhigh\");\n assert.equal(steps[2].model_provider, \"claude\");\n assert.equal(steps[2].model, \"claude-sonnet-4-6\");\n assert.equal(steps[2].parallel_group, \"final\");\n assert.deepEqual(steps[2].owned_paths, [\"README.md\"]);\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST2\"]);\n assert.equal(canEditStep(steps[2], false), true);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"stepModelSelectionPatch maps a selected model back to its provider\", () => {\n const modelCatalog = [\n {\n model: \"gpt-5.4\",\n display_name: \"GPT-5.4\",\n provider: \"openai\",\n },\n {\n model: \"claude-sonnet-4-6\",\n display_name: \"Claude Sonnet 4-6\",\n provider: \"claude\",\n },\n ];\n\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"claude-sonnet-4-6\"),\n {\n model_provider: \"claude\",\n model: \"claude-sonnet-4-6\",\n },\n );\n assert.deepEqual(\n stepModelSelectionPatch(modelCatalog, { execution_model: \"gpt-5.4\", model_slug_input: \"gpt-5.4\" }, \"gpt-5.4\"),\n {\n model_provider: \"\",\n model: \"\",\n },\n );\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "f614724d34b78b29c9932cc7a314c745ce7a72d3": {"t": 1785766897.136589, "n": 73720, "h": "979a55024462e6d2", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n ],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n ],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: checkpoint/);\n assert.match(report, /flow: checkpoint/);\n assert.match(report, /checkpoint: checkpoint/);\n assert.match(report, /process: running/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"deriveExecutionUiState ignores stale checkpoint state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.checkpointFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"setup_ready\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"running\",\n title: \"Review build\",\n },\n ],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [],\n },\n null,\n ), /consistent: yes/);\n});\n\ntest(\"deriveExecutionUiState lowers stale running status to the saved idle state when no visible execution job is active\", () => {\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:parallel\",\n },\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n checkpoints: {\n items: [],\n },\n },\n {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"pending\" },\n ],\n },\n null,\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"idle\");\n assert.equal(state.toolbarFamily, \"idle\");\n assert.equal(state.flowFamily, \"idle\");\n assert.equal(state.processFamily, \"idle\");\n assert.equal(state.displayStatusValue, \"plan_ready\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "7f08d3514c7df69389f195ba0c1ce17e23d55ac7": {"t": 1785766899.30755, "n": 70889, "h": "389977c7997aa640", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n deriveExecutionUiState,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n executionConsistencyReport,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"deriveExecutionUiState keeps toolbar, flow, and process aligned for a live run\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, true);\n assert.equal(state.displayFamily, \"running\");\n assert.equal(state.toolbarFamily, \"running\");\n assert.equal(state.flowFamily, \"running\");\n assert.equal(state.processFamily, \"running\");\n assert.equal(state.checkpointFamily, \"idle\");\n assert.match(executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n items: [],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n ), /consistent: yes/);\n});\n\ntest(\"execution consistency reports surface mismatches when checkpoint state diverges\", () => {\n const plan = {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n {\n step_id: \"ST1\",\n title: \"Build\",\n status: \"running\",\n },\n ],\n };\n const state = deriveExecutionUiState(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n ],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n const report = executionConsistencyReport(\n {\n project: {\n current_status: \"running:run-plan\",\n },\n plan,\n checkpoints: {\n pending: {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n items: [\n {\n checkpoint_id: \"CP1\",\n status: \"awaiting_review\",\n title: \"Review build\",\n },\n ],\n },\n },\n plan,\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(state.consistent, false);\n assert.equal(state.displayFamily, \"syncing\");\n assert.match(report, /toolbar: checkpoint/);\n assert.match(report, /flow: checkpoint/);\n assert.match(report, /checkpoint: checkpoint/);\n assert.match(report, /process: running/);\n assert.match(report, /display: syncing/);\n assert.match(report, /diff:/);\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "8bb489b35b1c6ebb8cae6d74c02a0f111dbf36d3": {"t": 1785766901.438558, "n": 67882, "h": "50cce17a1ed35a32", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "d227e714f60f3a73070cfb9564dc6a00ad7c6722": {"t": 1785766903.063672, "n": 69014, "h": "574f3f70621313b0", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n execution_model: \"gpt-5.4\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n execution_model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "dde0093a2767f1ca41b1d79041fef3d5aa042c1e": {"t": 1785766904.5823774, "n": 68824, "h": "bc99417fca482978", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "4b8f7d22ff0a483cecf8feff7892637746d7f968": {"t": 1785766906.156019, "n": 68982, "h": "5a50b38c731f9097", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n formatChatSessionTitle,\n formatCheckpointDisplayId,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectChatJobFromJobs,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n isChatJob,\n jobLane,\n jobLaneForRequest,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"formatChatSessionTitle strips txt suffixes and trailing timestamp slugs for UI labels\", () => {\n assert.equal(formatChatSessionTitle(\"코드 설명해줘 20260330101705.txt\", \"Conversation\"), \"코드 설명해줘\");\n assert.equal(formatChatSessionTitle(\"Release_notes-20260330101705.txt\", \"Conversation\"), \"Release_notes\");\n assert.equal(formatChatSessionTitle(\"Conversation.txt\", \"Conversation\"), \"Conversation\");\n assert.equal(formatChatSessionTitle(\"\", \"Conversation\"), \"Conversation\");\n});\n\ntest(\"formatCheckpointDisplayId aliases checkpoint labels to ST for the UI\", () => {\n assert.equal(formatCheckpointDisplayId(\"CP1\"), \"ST1\");\n assert.equal(formatCheckpointDisplayId(\"cp24\"), \"ST24\");\n assert.equal(formatCheckpointDisplayId(\"ST3\"), \"ST3\");\n assert.equal(formatCheckpointDisplayId(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"conversation\" }), \"chat\");\n assert.equal(jobLaneForRequest(\"send-chat-message\", { chat_mode: \"debugger\" }), \"execution\");\n assert.equal(jobLane(chatJob), \"chat\");\n assert.equal(isChatJob(chatJob), true);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"project chat job helpers keep execution and conversational chat lanes separate\", () => {\n const jobs = [\n {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 100,\n },\n {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n job_lane: \"chat\",\n chat_mode: \"review\",\n repo_id: \"repo-1\",\n updated_at_ms: 120,\n },\n {\n id: \"job-recovery\",\n status: \"queued\",\n command: \"send-chat-message\",\n job_lane: \"execution\",\n chat_mode: \"debugger\",\n repo_id: \"repo-2\",\n updated_at_ms: 140,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-run\");\n assert.equal(projectChatJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-chat\");\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-2\" })?.id, \"job-recovery\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings preserve the selected provider and runtime defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"gemini\");\n assert.equal(settings.model, \"gemini-3-flash-preview\");\n assert.equal(settings.model_slug_input, \"gemini-3-flash-preview\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"https://generativelanguage.googleapis.com\");\n assert.equal(settings.provider_api_key_env, \"GEMINI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath(\"gemini\"));\n});\n\ntest(\"program settings keep custom OpenAI model selections when they are already valid\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-4.1-mini\");\n assert.equal(settings.model_slug_input, \"gpt-4.1-mini\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "97ea149e653cebad19bce64c8f0baf74574aed7f": {"t": 1785766907.6859837, "n": 66848, "h": "6c20fd405fe5b4a6", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings preserve the selected provider and runtime defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"gemini\");\n assert.equal(settings.model, \"gemini-3-flash-preview\");\n assert.equal(settings.model_slug_input, \"gemini-3-flash-preview\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"https://generativelanguage.googleapis.com\");\n assert.equal(settings.provider_api_key_env, \"GEMINI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath(\"gemini\"));\n});\n\ntest(\"program settings keep custom OpenAI model selections when they are already valid\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-4.1-mini\");\n assert.equal(settings.model_slug_input, \"gpt-4.1-mini\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "2fd90730be560f19e50ce26f47008a30832fde2c": {"t": 1785766909.5441413, "n": 66852, "h": "e292834773f55ba1", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings preserve the selected provider and runtime defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"gemini\");\n assert.equal(settings.model, \"gemini-3-flash-preview\");\n assert.equal(settings.model_slug_input, \"gemini-3-flash-preview\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"https://generativelanguage.googleapis.com\");\n assert.equal(settings.provider_api_key_env, \"GEMINI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath(\"gemini\"));\n});\n\ntest(\"program settings keep custom OpenAI model selections when they are already valid\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-4.1-mini\");\n assert.equal(settings.model_slug_input, \"gpt-4.1-mini\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"auto\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "0df603a4201b763e83818d0e6ad46e2de2a874b7": {"t": 1785766911.2622306, "n": 66846, "h": "47df87c7ba1a434d", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n resolveProjectDirectory,\n runtimeSummary,\n isChatCommand,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings preserve the selected provider and runtime defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"gemini\");\n assert.equal(settings.model, \"gemini-3-flash-preview\");\n assert.equal(settings.model_slug_input, \"gemini-3-flash-preview\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"https://generativelanguage.googleapis.com\");\n assert.equal(settings.provider_api_key_env, \"GEMINI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath(\"gemini\"));\n});\n\ntest(\"program settings keep custom OpenAI model selections when they are already valid\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-4.1-mini\");\n assert.equal(settings.model_slug_input, \"gpt-4.1-mini\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"auto\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"auto\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"auto\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | faster planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | faster planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"resolveProjectDirectory prefers loaded project detail over a stale form value\", () => {\n assert.equal(\n resolveProjectDirectory(\n { project_dir: \"\" },\n {\n project: {\n repo_path: \"/workspace/demo\",\n },\n },\n ),\n \"/workspace/demo\",\n );\n assert.equal(resolveProjectDirectory({ project_dir: \"/workspace/fallback\" }, null), \"/workspace/fallback\");\n});\n\ntest(\"runtimeSummary falls back to model_slug_input when model is blank\", () => {\n assert.equal(\n runtimeSummary({ model: \"\", model_slug_input: \"gpt-5.4-mini\", effort: \"medium\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4-mini | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "3523547a403d58ebbc6bafed4972bd909bcf25a5": {"t": 1785766914.1969097, "n": 65975, "h": "763e13e52ff845af", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n runtimeSummary,\n isChatCommand,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"auto\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"auto\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"auto\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | compact planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | compact planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "ba0c89a326a60f67ce9018a3b4d14102e8822f63": {"t": 1785766915.671555, "n": 66133, "h": "4b20633f56099c0f", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n runtimeSummary,\n isChatCommand,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n chat_effort: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings preserve the selected provider and runtime defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"gemini\");\n assert.equal(settings.model, \"gemini-3-flash-preview\");\n assert.equal(settings.model_slug_input, \"gemini-3-flash-preview\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"https://generativelanguage.googleapis.com\");\n assert.equal(settings.provider_api_key_env, \"GEMINI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath(\"gemini\"));\n});\n\ntest(\"program settings keep custom OpenAI model selections when they are already valid\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-4.1-mini\");\n assert.equal(settings.model_slug_input, \"gpt-4.1-mini\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"auto\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"auto\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"auto\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | compact planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | compact planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "0d0105d27ded0fab5530da48f2b46b8f7144483d": {"t": 1785766917.3178487, "n": 65975, "h": "763e13e52ff845af", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n runtimeSummary,\n isChatCommand,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"auto\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"auto\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"auto\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\ntest(\"planStepsWithCloseout only depends on terminal steps in a DAG\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\", depends_on: [] },\n { step_id: \"ST2\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", status: \"completed\", depends_on: [\"ST1\"] },\n { step_id: \"ST4\", status: \"pending\", depends_on: [\"ST2\", \"ST3\"] },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps[4].step_id, \"CO1\");\n assert.deepEqual(steps[4].depends_on, [\"ST4\"]);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | compact planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | compact planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "06f8d9a95328de4a40d25bf866742cc9ec247332": {"t": 1785766918.9424644, "n": 65257, "h": "b3452c6499523b77", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n runtimeSummary,\n isChatCommand,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"auto\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"auto\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"auto\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | compact planning\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | compact planning\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "6c69a5ae40dd3a7eb49f3905a0cd10ad41bcc06a": {"t": 1785766921.031771, "n": 65235, "h": "a320b76ce419f289", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n runtimeSummary,\n isChatCommand,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n visibleExecutionJob,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"chat command helpers keep send-chat-message jobs out of execution widgets\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n };\n const runJob = {\n id: \"job-run\",\n status: \"running\",\n command: \"run-plan\",\n };\n\n assert.equal(isChatCommand(\"send-chat-message\"), true);\n assert.equal(isChatCommand(\"run-plan\"), false);\n assert.equal(visibleExecutionJob(chatJob), null);\n assert.equal(visibleExecutionJob(runJob), runJob);\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"auto\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"auto\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"auto\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | /fast\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | /fast\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "a9a4c71a8d141562ccd4b1895abc04f259d490fc": {"t": 1785766922.5710015, "n": 64693, "h": "4dab54b723136556", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n runtimeSummary,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"chat jobs do not affect execution status or progress\", () => {\n const chatJob = {\n id: \"job-chat\",\n status: \"running\",\n command: \"send-chat-message\",\n repo_id: \"repo-1\",\n };\n\n assert.equal(projectStatusWithJob(\"plan_ready\", chatJob), \"plan_ready\");\n\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"plan_ready\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n stats: {\n total_steps: 0,\n completed_steps: 0,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n },\n {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n chatJob,\n );\n\n assert.equal(progress.isActive, false);\n assert.equal(progress.command, \"\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"auto\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"auto\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"auto\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | /fast\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | /fast\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}, "2df34ff16285ff373fc18cd2ae23e5ecdf5ebf80": {"t": 1785766924.0465455, "n": 63826, "h": "748e85302d395baa", "c": "import assert from \"node:assert/strict\";\nimport test from \"node:test\";\n\nimport {\n activityLineSummary,\n applyConfigRuntimeModelSelection,\n applyProviderDefaults,\n applyProgramSettings,\n applyProgramSettingsToForm,\n autoRoutingPresetLabel,\n backgroundJobProjectKey,\n configReasoningOptions,\n basename,\n blankProjectForm,\n buildProjectPayload,\n buildRunPlanPayloadFromDetail,\n canEditStep,\n codexUsageBuckets,\n cloneValue,\n commandLabel,\n detailApplySignature,\n computePlanStats,\n CLAUDE_DEFAULT_MODEL,\n DEEPSEEK_DEFAULT_MODEL,\n defaultCodexPath,\n planStepsWithCloseout,\n deriveExecutionProgress,\n reasoningEffortLabel,\n deriveIdleProjectStatus,\n deriveGithubMode,\n effectiveStepStatus,\n executionProgressCaptionDisplay,\n firstSelectableStepId,\n GEMINI_DEFAULT_MODEL,\n GLM_DEFAULT_MODEL,\n inheritProjectIdentityForm,\n isDuplicateProjectJobError,\n isPlanningProgressRunning,\n KIMI_DEFAULT_MODEL,\n mergeProjectDetailCodexStatus,\n MINIMAX_DEFAULT_MODEL,\n normalizeMemoryBudgetGiB,\n normalizeInterruptedPlan,\n planDependencyValidationMessage,\n planningProgressCaptionDisplay,\n progressCaption,\n programSettingsFromRuntime,\n normalizedModelProvider,\n filterModelCatalogByProvider,\n providerAvailable,\n providerUsable,\n providerStatusReason,\n providerSupportsCatalog,\n projectJobFromJobs,\n projectFormFromDetail,\n projectStatusWithJob,\n runtimeSummary,\n QWEN_CODE_DEFAULT_MODEL,\n sanitizeProjectDetailForJobState,\n sanitizeProjectListForJobState,\n selectedConfigReasoning,\n shouldKeepUnsavedPlan,\n shouldShowEstimatedCost,\n shouldReplaceVisibleProject,\n statusTone,\n syncProgramSettingsModel,\n toolbarProgressCaptionDisplay,\n workspaceStatsFromProjects,\n} from \"../src/utils.js\";\n\ntest(\"cloneValue deep-clones plain data and preserves nullish values\", () => {\n const original = {\n runtime: {\n model: \"gpt-5.4\",\n tags: [\"desktop\", \"tests\"],\n },\n };\n\n const cloned = cloneValue(original);\n cloned.runtime.tags.push(\"changed\");\n\n assert.deepEqual(original.runtime.tags, [\"desktop\", \"tests\"]);\n assert.deepEqual(cloned.runtime.tags, [\"desktop\", \"tests\", \"changed\"]);\n assert.equal(cloneValue(null), null);\n assert.equal(cloneValue(undefined), undefined);\n});\n\ntest(\"basename handles Windows, POSIX, and empty paths\", () => {\n assert.equal(basename(\"C:\\\\work\\\\repo\"), \"repo\");\n assert.equal(basename(\"/tmp/demo/\"), \"demo\");\n assert.equal(basename(\"\"), \"\");\n});\n\ntest(\"detailApplySignature tracks payload identity and running job state\", () => {\n const detail = {\n detail_level: \"core\",\n detail_signature: \"sig-123\",\n project: {\n repo_id: \"repo-1\",\n current_status: \"running:block:2\",\n },\n };\n\n assert.equal(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n \"repo-1|core|sig-123|running:block:2|job-1|running\",\n );\n assert.notEqual(\n detailApplySignature(detail, { id: \"job-1\", status: \"running\" }),\n detailApplySignature(detail, { id: \"job-2\", status: \"running\" }),\n );\n});\n\ntest(\"project job helpers match jobs by repo id or project path and derive display status\", () => {\n const jobs = [\n {\n id: \"job-queued\",\n status: \"queued\",\n command: \"run-plan\",\n project_dir: \"C:\\\\Work\\\\Repo\",\n updated_at_ms: 20,\n },\n {\n id: \"job-running\",\n status: \"running\",\n command: \"generate-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: 10,\n },\n ];\n\n assert.equal(projectJobFromJobs(jobs, { repo_id: \"repo-1\" })?.id, \"job-running\");\n assert.equal(projectJobFromJobs(jobs, { repo_path: \"c:/work/repo\" })?.id, \"job-queued\");\n assert.equal(projectStatusWithJob(\"plan_ready\", jobs[0]), \"queued:run-plan\");\n assert.equal(projectStatusWithJob(\"setup_ready\", jobs[1]), \"running:generate-plan\");\n});\n\ntest(\"backgroundJobProjectKey normalizes workspace and project paths for deduping\", () => {\n assert.equal(\n backgroundJobProjectKey(\n {\n project_dir: \"C:\\\\Work\\\\Repo\",\n },\n \"C:\\\\Users\\\\alber\\\\Workspace\",\n ),\n \"c:/users/alber/workspace||c:/work/repo\",\n );\n assert.equal(\n backgroundJobProjectKey(\n {\n repo_id: \"repo-1\",\n },\n \"/tmp/workspace\",\n ),\n \"/tmp/workspace|repo-1|\",\n );\n assert.equal(backgroundJobProjectKey({}, \"/tmp/workspace\"), \"\");\n});\n\ntest(\"commandLabel maps manual recovery commands to readable labels\", () => {\n assert.equal(commandLabel(\"run-manual-debugger\", \"en\"), \"Manual Debugger\");\n assert.equal(commandLabel(\"run-manual-merger\", \"en\"), \"Manual Merger\");\n assert.equal(commandLabel(\"run-manual-debugger\", \"ko\"), \"수동 디버거\");\n assert.equal(commandLabel(\"run-manual-merger\", \"ko\"), \"수동 머저\");\n});\n\ntest(\"isDuplicateProjectJobError detects bridge rejections for already-active jobs\", () => {\n assert.equal(isDuplicateProjectJobError(\"Another background task is already active for this project.\"), true);\n assert.equal(isDuplicateProjectJobError(new Error(\"another background task is already active for this project.\")), true);\n assert.equal(isDuplicateProjectJobError(\"The requested background job was not found.\"), false);\n});\n\ntest(\"planDependencyValidationMessage reports dependency cycles with step ids\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [\n { step_id: \"ST1\", depends_on: [\"ST2\"] },\n { step_id: \"ST2\", depends_on: [\"ST1\"] },\n ],\n }),\n \"Parallel execution plan contains a dependency cycle: ST1 -> ST2 -> ST1.\",\n );\n});\n\ntest(\"planDependencyValidationMessage reports unknown dependency references\", () => {\n assert.equal(\n planDependencyValidationMessage({\n steps: [{ step_id: \"ST1\", depends_on: [\"ST9\"] }],\n }),\n \"Unknown dependency reference: ST9\",\n );\n});\n\ntest(\"project job helpers ignore stale running jobs when the project has a newer saved state\", () => {\n const jobs = [\n {\n id: \"job-running\",\n status: \"running\",\n command: \"run-plan\",\n repo_id: \"repo-1\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n },\n ];\n\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n }),\n null,\n );\n assert.equal(\n projectJobFromJobs(jobs, {\n repo_id: \"repo-1\",\n current_status: \"plan_ready\",\n last_run_at: \"2026-03-27T02:59:59Z\",\n })?.id,\n \"job-running\",\n );\n});\n\ntest(\"deriveGithubMode distinguishes manual and existing projects\", () => {\n assert.equal(deriveGithubMode(\"https://github.com/openai/jakal-flow\"), \"manual\");\n assert.equal(deriveGithubMode(\"\"), \"existing\");\n assert.equal(deriveGithubMode(null), \"existing\");\n});\n\ntest(\"defaultCodexPath follows the current platform\", () => {\n assert.equal(defaultCodexPath(), process.platform === \"win32\" ? \"codex.cmd\" : \"codex\");\n assert.equal(defaultCodexPath(\"claude\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n assert.equal(defaultCodexPath(\"gemini\"), process.platform === \"win32\" ? \"gemini.cmd\" : \"gemini\");\n assert.equal(defaultCodexPath(\"qwen_code\"), process.platform === \"win32\" ? \"qwen.cmd\" : \"qwen\");\n assert.equal(defaultCodexPath(\"deepseek\"), process.platform === \"win32\" ? \"claude.cmd\" : \"claude\");\n});\n\ntest(\"program settings helpers keep global runtime controls separate from project-specific values\", () => {\n const settings = programSettingsFromRuntime({\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n allow_push: false,\n });\n\n assert.deepEqual(settings, {\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n billing_mode: \"included\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n developer_mode: false,\n ui_theme: \"dark\",\n dashboard_visibility: {\n status: true,\n remaining_steps: true,\n checkpoint_pending: false,\n input_tokens: false,\n output_tokens: false,\n estimated_remaining: true,\n estimated_cost: false,\n actual_cost: false,\n codex_plan: false,\n rate_limit_window_5h: false,\n rate_limit_window_7d: true,\n rate_limit_codex_spark: false,\n runtime_card: false,\n codex_usage_card: false,\n word_report_card: true,\n },\n background_concurrency_limit: 2,\n });\n\n assert.deepEqual(\n applyProgramSettings(\n {\n test_cmd: \"pytest -q\",\n },\n settings,\n ),\n {\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n model: \"gpt-5.4\",\n chat_model: \"\",\n planning_effort: \"medium\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n );\n\n assert.deepEqual(\n applyProgramSettingsToForm(\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n approval_mode: \"never\",\n },\n },\n settings,\n ),\n {\n project_dir: \"demo\",\n runtime: {\n model: \"gpt-5.4\",\n chat_model: \"\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n test_cmd: \"pytest -q\",\n model_provider: \"openai\",\n local_model_provider: \"ollama\",\n chat_model_provider: \"\",\n chat_local_model_provider: \"\",\n provider_base_url: \"\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n planning_effort: \"medium\",\n approval_mode: \"untrusted\",\n sandbox_mode: \"workspace-write\",\n checkpoint_interval_blocks: 1,\n codex_path: defaultCodexPath(),\n allow_push: false,\n require_checkpoint_approval: false,\n workflow_mode: \"standard\",\n ml_max_cycles: 3,\n execution_mode: \"parallel\",\n parallel_worker_mode: \"auto\",\n parallel_workers: 0,\n parallel_memory_per_worker_gib: 3,\n save_project_logs: false,\n },\n },\n );\n});\n\ntest(\"program settings normalize provider selection to GPT Codex only defaults\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"gemini\",\n model: \"gemini-3-flash-preview\",\n model_slug_input: \"gemini-3-flash-preview\",\n chat_model_provider: \"gemini\",\n chat_model: \"gemini-3-flash-preview\",\n provider_base_url: \"https://generativelanguage.googleapis.com\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.chat_model_provider, \"gemini\");\n assert.equal(settings.chat_model, \"gemini-3-flash-preview\");\n assert.equal(settings.provider_base_url, \"\");\n assert.equal(settings.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(settings.codex_path, defaultCodexPath());\n});\n\ntest(\"program settings reset custom OpenAI model selections back to GPT Codex only\", () => {\n const settings = programSettingsFromRuntime({\n model_provider: \"openai\",\n model: \"gpt-4.1-mini\",\n model_slug_input: \"gpt-4.1-mini\",\n codex_path: \"C:\\\\tools\\\\codex.cmd\",\n });\n\n assert.equal(settings.model_provider, \"openai\");\n assert.equal(settings.model, \"gpt-5.4\");\n assert.equal(settings.model_slug_input, \"gpt-5.4\");\n assert.equal(settings.codex_path, \"C:\\\\tools\\\\codex.cmd\");\n});\n\ntest(\"blankProjectForm seeds runtime defaults without mutating the source runtime\", () => {\n const defaultRuntime = {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n };\n\n const form = blankProjectForm(defaultRuntime);\n form.runtime.test_cmd = \"npm test\";\n\n assert.deepEqual(defaultRuntime, {\n model_preset: \"default\",\n generate_word_report: false,\n max_blocks: 9,\n test_cmd: \"pytest -q\",\n });\n assert.equal(form.branch, \"main\");\n assert.equal(form.github_mode, \"existing\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.max_blocks, 9);\n assert.equal(form.runtime.generate_word_report, false);\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"blankProjectForm falls back to repository defaults when runtime is missing\", () => {\n const form = blankProjectForm(null);\n\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_preset, \"\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.allow_background_queue, true);\n assert.equal(form.runtime.background_queue_priority, 0);\n});\n\ntest(\"providerSupportsCatalog enables curated catalogs for first-party provider presets\", () => {\n assert.equal(providerSupportsCatalog(\"openai\"), true);\n assert.equal(providerSupportsCatalog(\"gemini\"), true);\n assert.equal(providerSupportsCatalog(\"claude\"), true);\n assert.equal(providerSupportsCatalog(\"ollama\"), true);\n assert.equal(providerSupportsCatalog(\"deepseek\"), true);\n assert.equal(providerSupportsCatalog(\"openrouter\"), false);\n assert.equal(providerSupportsCatalog(\"local_openai\"), false);\n});\n\ntest(\"provider availability helpers read provider statuses from codex payloads\", () => {\n const codexStatus = {\n provider_statuses: {\n claude: {\n available: false,\n usable: false,\n reason: \"Claude Code is not installed.\",\n },\n gemini: {\n available: true,\n usable: true,\n reason: \"Gemini CLI is available.\",\n },\n },\n };\n\n assert.equal(providerAvailable(\"claude\", codexStatus), false);\n assert.equal(providerAvailable(\"gemini\", codexStatus), true);\n assert.equal(providerUsable(\"claude\", codexStatus), false);\n assert.equal(providerUsable(\"gemini\", codexStatus), true);\n assert.equal(providerStatusReason(\"claude\", codexStatus), \"Claude Code is not installed.\");\n});\n\ntest(\"ollama provider helpers treat legacy oss plus ollama projects as a first-class Ollama selection\", () => {\n const runtime = {\n model_provider: \"oss\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n };\n const modelCatalog = [\n { provider: \"oss\", local_provider: \"ollama\", model: \"qwen2.5-coder:0.5b\", display_name: \"Qwen 0.5B\" },\n { provider: \"oss\", local_provider: \"lmstudio\", model: \"local-model\", display_name: \"LM Studio Model\" },\n ];\n\n assert.equal(normalizedModelProvider(runtime), \"ollama\");\n assert.deepEqual(\n filterModelCatalogByProvider(modelCatalog, runtime).map((item) => item.model),\n [\"qwen2.5-coder:0.5b\"],\n );\n});\n\ntest(\"applyProviderDefaults drops the auto sentinel for providers without auto routing\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"auto\",\n model_slug_input: \"auto\",\n model_preset: \"auto\",\n },\n \"openrouter\",\n );\n\n assert.equal(runtime.model_provider, \"openrouter\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.model_preset, \"\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Gemini CLI and clears OpenAI-only defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"gemini\",\n );\n\n assert.equal(runtime.model_provider, \"gemini\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"gemini\"));\n assert.equal(runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"GEMINI_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Claude Code and applies Anthropic defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"claude\",\n );\n\n assert.equal(runtime.model_provider, \"claude\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"claude\"));\n assert.equal(runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n assert.equal(runtime.provider_api_key_env, \"ANTHROPIC_API_KEY\");\n});\n\ntest(\"applyProviderDefaults switches the runtime path for Qwen Code and applies DashScope defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"qwen_code\",\n );\n\n assert.equal(runtime.model_provider, \"qwen_code\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"qwen_code\"));\n assert.equal(runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://dashscope.aliyuncs.com/compatible-mode/v1\");\n assert.equal(runtime.provider_api_key_env, \"DASHSCOPE_API_KEY\");\n});\n\ntest(\"applyProviderDefaults seeds Claude-compatible vendor defaults\", () => {\n const deepseek = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"deepseek\",\n );\n const minimax = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"minimax\",\n );\n const glm = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"glm\",\n );\n\n assert.equal(deepseek.codex_path, defaultCodexPath(\"deepseek\"));\n assert.equal(deepseek.model, DEEPSEEK_DEFAULT_MODEL);\n assert.equal(deepseek.provider_api_key_env, \"DEEPSEEK_API_KEY\");\n assert.equal(deepseek.provider_base_url, \"https://api.deepseek.com/anthropic\");\n assert.equal(minimax.model, MINIMAX_DEFAULT_MODEL);\n assert.equal(minimax.provider_base_url, \"https://api.minimax.io/anthropic/v1\");\n assert.equal(glm.model, GLM_DEFAULT_MODEL);\n assert.equal(glm.provider_base_url, \"https://open.bigmodel.cn/api/anthropic\");\n});\n\ntest(\"applyProviderDefaults seeds Kimi defaults on the Codex/OpenAI-compatible path\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"kimi\",\n );\n\n assert.equal(runtime.model_provider, \"kimi\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, KIMI_DEFAULT_MODEL);\n assert.equal(runtime.provider_base_url, \"https://api.moonshot.cn/v1\");\n assert.equal(runtime.provider_api_key_env, \"MOONSHOT_API_KEY\");\n});\n\ntest(\"applyProviderDefaults promotes Ollama to a first-class local provider without keeping cloud models\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ollama\",\n );\n\n assert.equal(runtime.model_provider, \"ollama\");\n assert.equal(runtime.local_model_provider, \"ollama\");\n assert.equal(runtime.model, \"\");\n assert.equal(runtime.model_slug_input, \"\");\n assert.equal(runtime.codex_path, defaultCodexPath(\"ollama\"));\n});\n\ntest(\"applyProviderDefaults switches the runtime path for the ensemble provider and keeps Codex defaults\", () => {\n const runtime = applyProviderDefaults(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_slug_input: \"gpt-5.4\",\n codex_path: defaultCodexPath(),\n },\n \"ensemble\",\n );\n\n assert.equal(runtime.model_provider, \"ensemble\");\n assert.equal(runtime.codex_path, defaultCodexPath());\n assert.equal(runtime.model, \"gpt-5.4\");\n assert.equal(runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Gemini CLI projects on the Gemini default model\", () => {\n const form = blankProjectForm({\n model_provider: \"gemini\",\n provider_api_key_env: \"GEMINI_API_KEY\",\n codex_path: defaultCodexPath(\"gemini\"),\n });\n\n assert.equal(form.runtime.model_provider, \"gemini\");\n assert.equal(form.runtime.model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, GEMINI_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Claude Code projects on the Claude default model\", () => {\n const form = blankProjectForm({\n model_provider: \"claude\",\n provider_api_key_env: \"ANTHROPIC_API_KEY\",\n codex_path: defaultCodexPath(\"claude\"),\n });\n\n assert.equal(form.runtime.model_provider, \"claude\");\n assert.equal(form.runtime.model, CLAUDE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps Qwen Code projects on the Qwen default model\", () => {\n const form = blankProjectForm({\n model_provider: \"qwen_code\",\n provider_api_key_env: \"DASHSCOPE_API_KEY\",\n codex_path: defaultCodexPath(\"qwen_code\"),\n });\n\n assert.equal(form.runtime.model_provider, \"qwen_code\");\n assert.equal(form.runtime.model, QWEN_CODE_DEFAULT_MODEL);\n assert.equal(form.runtime.model_slug_input, QWEN_CODE_DEFAULT_MODEL);\n});\n\ntest(\"blankProjectForm keeps ensemble projects on the Codex default model\", () => {\n const form = blankProjectForm({\n model_provider: \"ensemble\",\n provider_api_key_env: \"OPENAI_API_KEY\",\n codex_path: defaultCodexPath(),\n });\n\n assert.equal(form.runtime.model_provider, \"ensemble\");\n assert.equal(form.runtime.model, \"gpt-5.4\");\n assert.equal(form.runtime.model_slug_input, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_openai_model, \"gpt-5.4\");\n assert.equal(form.runtime.ensemble_gemini_model, GEMINI_DEFAULT_MODEL);\n assert.equal(form.runtime.ensemble_claude_model, CLAUDE_DEFAULT_MODEL);\n});\n\ntest(\"normalizeMemoryBudgetGiB keeps one decimal place for UI memory budgets\", () => {\n assert.equal(normalizeMemoryBudgetGiB(\"1.54\", 3), 1.5);\n assert.equal(normalizeMemoryBudgetGiB(\"1.55\", 3), 1.6);\n assert.equal(normalizeMemoryBudgetGiB(\"0\", 3), 0.1);\n assert.equal(normalizeMemoryBudgetGiB(\"bogus\", 1.5), 1.5);\n});\n\ntest(\"projectFormFromDetail merges persisted runtime and derives GitHub mode\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n };\n const defaultRuntime = {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n };\n\n const form = projectFormFromDetail(detail, defaultRuntime);\n\n assert.deepEqual(form, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n optimization_mode: \"refactor\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n test_cmd: \"npm run check\",\n model: \"gpt-5.4\",\n },\n });\n});\n\ntest(\"inheritProjectIdentityForm keeps project links but resets runtime to app defaults\", () => {\n const form = inheritProjectIdentityForm(\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n runtime: {\n model: \"gpt-5.4-mini\",\n effort: \"high\",\n parallel_memory_per_worker_gib: 7,\n test_cmd: \"npm test\",\n },\n },\n {\n model: \"auto\",\n effort: \"medium\",\n parallel_memory_per_worker_gib: 3,\n test_cmd: \"python -m pytest\",\n optimization_mode: \"light\",\n },\n );\n\n assert.deepEqual(\n {\n project_dir: form.project_dir,\n display_name: form.display_name,\n branch: form.branch,\n origin_url: form.origin_url,\n github_mode: form.github_mode,\n },\n {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n github_mode: \"manual\",\n },\n );\n assert.equal(form.runtime.model, \"auto\");\n assert.equal(form.runtime.effort, \"medium\");\n assert.equal(form.runtime.parallel_memory_per_worker_gib, 3);\n assert.equal(form.runtime.test_cmd, \"python -m pytest\");\n assert.equal(form.runtime.optimization_mode, \"light\");\n assert.equal(form.runtime.generate_word_report, true);\n assert.equal(form.runtime.max_blocks, 5);\n assert.equal(form.runtime.execution_mode, \"parallel\");\n assert.equal(form.runtime.model_slug_input, \"auto\");\n assert.equal(form.runtime.model_provider, \"openai\");\n assert.equal(form.runtime.local_model_provider, \"ollama\");\n assert.equal(form.runtime.approval_mode, \"never\");\n assert.equal(form.runtime.sandbox_mode, \"danger-full-access\");\n assert.equal(form.runtime.allow_push, true);\n assert.equal(form.runtime.workflow_mode, \"standard\");\n assert.equal(form.runtime.parallel_worker_mode, \"auto\");\n assert.equal(form.runtime.parallel_workers, 0);\n assert.equal(form.runtime.ml_max_cycles, 3);\n assert.equal(form.runtime.checkpoint_interval_blocks, 1);\n assert.equal(form.runtime.require_checkpoint_approval, false);\n assert.equal(form.runtime.model_preset, \"auto\");\n assert.equal(form.runtime.model_selection_mode, \"slug\");\n assert.equal(form.runtime.planning_effort, \"medium\");\n assert.equal(form.runtime.provider_base_url, \"\");\n assert.equal(form.runtime.provider_api_key_env, \"OPENAI_API_KEY\");\n assert.equal(form.runtime.codex_path, defaultCodexPath());\n});\n\ntest(\"mergeProjectDetailCodexStatus preserves the last known catalog when a lightweight detail omits it\", () => {\n const fallbackStatus = {\n available: true,\n model_catalog: [{ model: \"auto\", display_name: \"Auto\" }],\n account: { email: \"demo@example.com\" },\n };\n const merged = mergeProjectDetailCodexStatus(\n {\n project: { repo_id: \"repo-a\" },\n codex_status: {},\n snapshot: {},\n bottom_panels: {},\n },\n fallbackStatus,\n fallbackStatus.model_catalog,\n );\n\n assert.deepEqual(merged.codex_status, fallbackStatus);\n assert.deepEqual(merged.snapshot.codex_status, fallbackStatus);\n assert.deepEqual(merged.bottom_panels.codex_status, fallbackStatus);\n});\n\ntest(\"shouldKeepUnsavedPlan only preserves local edits for the same project\", () => {\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", true), true);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-b\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"\", true), false);\n assert.equal(shouldKeepUnsavedPlan(\"repo-a\", \"repo-a\", false), false);\n});\n\ntest(\"shouldReplaceVisibleProject only accepts a completed job for the visible project\", () => {\n assert.equal(shouldReplaceVisibleProject(\"\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-a\"), true);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"repo-b\"), false);\n assert.equal(shouldReplaceVisibleProject(\"repo-a\", \"\"), false);\n});\n\ntest(\"running-state helpers fall back to idle project status when no job is active\", () => {\n const plan = {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\" },\n ],\n };\n const normalizedPlan = normalizeInterruptedPlan(plan);\n\n assert.deepEqual(computePlanStats(plan), {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n });\n assert.deepEqual(normalizedPlan, {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n });\n assert.equal(deriveIdleProjectStatus(normalizedPlan, null, \"running:block:2\"), \"plan_ready\");\n assert.deepEqual(\n workspaceStatsFromProjects([\n { status: \"plan_ready\" },\n { status: \"failed\" },\n { status: \"running:block:3\" },\n ]),\n {\n project_count: 3,\n ready_like: 1,\n running: 1,\n failed: 1,\n },\n );\n});\n\ntest(\"job-aware sanitizers clear stale running status without touching active jobs\", () => {\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n snapshot: {\n project: {\n current_status: \"running:block:2\",\n },\n plan: {\n closeout_status: \"running\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\" },\n ],\n },\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(runningDetail, null);\n const activeDetail = sanitizeProjectDetailForJobState(runningDetail, { id: \"job-1\", status: \"running\" });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"plan_ready\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"not_started\");\n assert.equal(sanitizedDetail.plan.steps[1].status, \"pending\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"plan_ready\");\n assert.equal(activeDetail.project.current_status, \"running:block:2\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware detail sanitizer ignores a stale running bridge job when saved project state is newer\", () => {\n const detail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"closed_out\",\n last_run_at: \"2026-03-27T03:25:31Z\",\n },\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 1,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, {\n id: \"job-1\",\n status: \"running\",\n repo_id: \"repo-a\",\n updated_at_ms: Date.parse(\"2026-03-27T03:00:00Z\"),\n });\n\n assert.equal(sanitizedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedDetail.plan.closeout_status, \"completed\");\n});\n\ntest(\"job-aware detail sanitizer prefers terminal plan state over recent running activity\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const failedDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | step-started [ST2] | Running ST2: Build the screen\"],\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"failed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 1,\n running_steps: 0,\n remaining_steps: 1,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:block:2\",\n },\n },\n };\n const completedDetail = {\n project: {\n repo_id: \"repo-b\",\n current_status: \"running:closeout\",\n last_run_at: \"2026-03-26T09:59:58Z\",\n },\n activity: [\"2026-03-26T09:59:59Z | closeout-started | Started project closeout.\"],\n plan: {\n closeout_status: \"completed\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 2,\n failed_steps: 0,\n running_steps: 0,\n remaining_steps: 0,\n },\n bottom_panels: {\n git_status: {\n current_status: \"running:closeout\",\n },\n },\n };\n\n const sanitizedFailedDetail = sanitizeProjectDetailForJobState(failedDetail, null, { nowMs });\n const sanitizedCompletedDetail = sanitizeProjectDetailForJobState(completedDetail, null, { nowMs });\n\n assert.equal(sanitizedFailedDetail.project.current_status, \"failed\");\n assert.equal(sanitizedFailedDetail.bottom_panels.git_status.current_status, \"failed\");\n assert.equal(sanitizedCompletedDetail.project.current_status, \"closed_out\");\n assert.equal(sanitizedCompletedDetail.bottom_panels.git_status.current_status, \"closed_out\");\n});\n\ntest(\"job-aware detail sanitizer marks planning progress as generate-plan when the job snapshot is missing\", () => {\n const detail = {\n project: {\n repo_id: \"repo-plan\",\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n bottom_panels: {\n git_status: {\n current_status: \"setup_ready\",\n },\n },\n };\n\n const sanitizedDetail = sanitizeProjectDetailForJobState(detail, null);\n\n assert.equal(sanitizedDetail.project.current_status, \"running:generate-plan\");\n assert.equal(sanitizedDetail.bottom_panels.git_status.current_status, \"running:generate-plan\");\n});\n\ntest(\"job-aware detail sanitizer preserves a very recent active run signal while the job snapshot catches up\", () => {\n const nowMs = Date.parse(\"2026-03-26T10:00:00Z\");\n const runningDetail = {\n project: {\n repo_id: \"repo-a\",\n current_status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n },\n activity: [\"2026-03-26T09:59:42Z | step-started [ST2] | Running ST2: Build the screen\"],\n checkpoints: {\n pending: {\n checkpoint_id: \"CP2\",\n status: \"awaiting_review\",\n },\n },\n plan: {\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"running\", started_at: \"2026-03-26T09:59:35Z\" },\n ],\n },\n };\n const runningList = [\n {\n repo_id: \"repo-a\",\n status: \"running:block:2\",\n last_run_at: \"2026-03-26T09:59:40Z\",\n stats: { total_steps: 2, completed_steps: 1, failed_steps: 0, running_steps: 1, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const preservedDetail = sanitizeProjectDetailForJobState(runningDetail, null, { nowMs });\n const sanitizedList = sanitizeProjectListForJobState(runningList, null, { nowMs });\n\n assert.equal(preservedDetail.project.current_status, \"running:block:2\");\n assert.equal(preservedDetail.plan.steps[1].status, \"running\");\n assert.equal(sanitizedList[0].status, \"plan_ready\");\n});\n\ntest(\"job-aware sanitizers overlay queued and running status from multiple active jobs\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"plan_ready\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n {\n repo_id: \"repo-b\",\n repo_path: \"C:/work/repo-b\",\n status: \"setup_ready\",\n stats: { total_steps: 0, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 0 },\n closeout_status: \"not_started\",\n },\n ];\n const jobs = [\n { id: \"job-1\", status: \"queued\", command: \"run-plan\", project_dir: \"C:\\\\work\\\\repo-a\", updated_at_ms: 20 },\n { id: \"job-2\", status: \"running\", command: \"generate-plan\", repo_id: \"repo-b\", updated_at_ms: 10 },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, jobs);\n\n assert.equal(nextProjects[0].status, \"queued:run-plan\");\n assert.equal(nextProjects[1].status, \"running:generate-plan\");\n});\n\ntest(\"job-aware sanitizers clear stale queued overlays when reservations disappear\", () => {\n const projects = [\n {\n repo_id: \"repo-a\",\n repo_path: \"C:/work/repo-a\",\n status: \"queued:run-plan\",\n stats: { total_steps: 1, completed_steps: 0, failed_steps: 0, running_steps: 0, remaining_steps: 1 },\n closeout_status: \"not_started\",\n },\n ];\n\n const nextProjects = sanitizeProjectListForJobState(projects, []);\n\n assert.equal(nextProjects[0].status, \"plan_ready\");\n});\n\ntest(\"activityLineSummary strips the timestamp and event prefix\", () => {\n assert.equal(\n activityLineSummary(\"2026-03-26T09:00:00Z | step-started [ST2] | Running ST2: Build the screen\"),\n \"Running ST2: Build the screen\",\n );\n assert.equal(activityLineSummary(\"single message\"), \"single message\");\n assert.equal(activityLineSummary(\"\"), \"\");\n});\n\ntest(\"deriveExecutionProgress summarizes active step progress and recent activity\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:block:2\",\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 2,\n },\n activity: [\n \"2026-03-26T09:01:00Z | step-started [ST2] | Running ST2: Build the screen\",\n \"2026-03-26T09:00:00Z | batch-started | Running parallel batch: ST2, ST3\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"pending\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"step\");\n assert.equal(progress.runningStep.step_id, \"ST2\");\n assert.deepEqual(progress.readyIds, [\"ST2\", \"ST3\"]);\n assert.equal(progress.completedProgressUnits, 1);\n assert.equal(progress.totalProgressUnits, 4);\n assert.equal(progress.percent, 25);\n assert.equal(progress.headlineActivity, \"Running ST2: Build the screen\");\n});\n\ntest(\"deriveExecutionProgress falls back to an indeterminate planning state\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:generate-plan\",\n },\n activity: [\"2026-03-26T09:02:00Z | plan-generated | Drafting execution plan\"],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, true);\n assert.equal(progress.totalSteps, 0);\n});\n\ntest(\"deriveExecutionProgress uses structured planning progress when available\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | planner-agent-started | Planner Agent A is decomposing the work into implementation blocks.\",\n ],\n planning_progress: {\n stage_count: 4,\n completed_stages: 1,\n percent: 38,\n current_stage_key: \"planner_a\",\n current_stage_index: 2,\n current_stage_label: \"Planner Agent A\",\n current_stage_status: \"running\",\n current_agent_label: \"Planner Agent A\",\n message: \"Planner Agent A is decomposing the work into implementation blocks.\",\n stages: [\n { key: \"context_scan\", index: 1, label: \"Scan repository context\", status: \"completed\" },\n { key: \"planner_a\", index: 2, label: \"Planner Agent A\", status: \"running\", agent_label: \"Planner Agent A\" },\n { key: \"planner_b\", index: 3, label: \"Planner Agent B\", status: \"pending\" },\n { key: \"finalize\", index: 4, label: \"Validate and save plan\", status: \"pending\" },\n ],\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n {\n status: \"running\",\n command: \"generate-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.percent, 38);\n assert.equal(progress.planningStageCount, 4);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n assert.equal(progress.planningCurrentAgentLabel, \"Planner Agent A\");\n assert.equal(progress.headlineActivity, \"Planner Agent A is decomposing the work into implementation blocks.\");\n});\n\ntest(\"deriveExecutionProgress treats running planning progress as active even without a bridge job\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"setup_ready\",\n },\n planning_progress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n current_stage_label: \"Planner Agent A\",\n },\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [],\n },\n },\n null,\n null,\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"planning\");\n assert.equal(progress.indeterminate, false);\n assert.equal(progress.planningCurrentStage.label, \"Planner Agent A\");\n});\n\ntest(\"planningProgressCaptionDisplay reports the active planning stage and status\", () => {\n assert.equal(isPlanningProgressRunning({ current_stage_status: \"running\" }), true);\n assert.equal(isPlanningProgressRunning({ currentStageStatus: \"completed\" }), false);\n assert.equal(\n planningProgressCaptionDisplay({\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n }),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n planningProgressCaptionDisplay(null),\n \"Generating execution plan\",\n );\n});\n\ntest(\"syncProgramSettingsModel mirrors project model changes back into program defaults\", () => {\n const nextSettings = syncProgramSettingsModel(\n {\n model_provider: \"openai\",\n model: \"gpt-5.4\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gpt-5.4\",\n ensemble_openai_model: \"gpt-5.4\",\n ensemble_gemini_model: GEMINI_DEFAULT_MODEL,\n ensemble_claude_model: CLAUDE_DEFAULT_MODEL,\n },\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-pro\",\n model_preset: \"\",\n model_selection_mode: \"slug\",\n model_slug_input: \"gemini-2.5-pro\",\n ensemble_openai_model: \"gpt-5.4-mini\",\n ensemble_gemini_model: \"gemini-2.5-pro\",\n ensemble_claude_model: \"claude-3.7-sonnet\",\n },\n );\n\n assert.equal(nextSettings.model_provider, \"gemini\");\n assert.equal(nextSettings.model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.model_slug_input, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_openai_model, \"gpt-5.4-mini\");\n assert.equal(nextSettings.ensemble_gemini_model, \"gemini-2.5-pro\");\n assert.equal(nextSettings.ensemble_claude_model, \"claude-3.7-sonnet\");\n});\n\ntest(\"deriveExecutionProgress marks debugger recovery as an active debugging phase\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:debugging\",\n },\n activity: [\n \"debugger | debugger_invoked | Debugging ST2 - Build | python -m pytest exited with 1\",\n ],\n plan: {\n execution_mode: \"serial\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Build\", status: \"running\" },\n ],\n },\n stats: {\n total_steps: 2,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 1,\n remaining_steps: 1,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.equal(progress.phase, \"debugging\");\n assert.equal(progress.debugging, true);\n assert.equal(progress.status, \"running:debugging\");\n assert.equal(progress.headlineActivity, \"Debugging ST2 - Build | python -m pytest exited with 1\");\n});\n\ntest(\"deriveExecutionProgress keeps integrating steps in the active execution set\", () => {\n const progress = deriveExecutionProgress(\n {\n project: {\n current_status: \"running:parallel\",\n },\n activity: [\n \"2026-03-26T09:02:00Z | batch-started | Integrating ST2 while ST3 keeps running\",\n ],\n plan: {\n execution_mode: \"parallel\",\n closeout_status: \"not_started\",\n steps: [\n { step_id: \"ST1\", title: \"Plan\", status: \"completed\" },\n { step_id: \"ST2\", title: \"Integrate\", status: \"integrating\", depends_on: [\"ST1\"] },\n { step_id: \"ST3\", title: \"Backend\", status: \"running\", depends_on: [\"ST1\"] },\n ],\n },\n stats: {\n total_steps: 3,\n completed_steps: 1,\n failed_steps: 0,\n running_steps: 2,\n remaining_steps: 2,\n },\n },\n null,\n {\n status: \"running\",\n command: \"run-plan\",\n },\n );\n\n assert.equal(progress.isActive, true);\n assert.deepEqual(progress.runningStepList.map((step) => step.step_id), [\"ST2\", \"ST3\"]);\n assert.equal(progress.runningStep?.status, \"integrating\");\n});\n\ntest(\"buildProjectPayload trims fields, blanks origin_url for existing repos, and clones plan data\", () => {\n const form = {\n project_dir: \" C:/work/demo \",\n display_name: \" Demo App \",\n branch: \" \",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"existing\",\n runtime: {\n test_cmd: \"pytest -q\",\n },\n };\n const plan = {\n steps: [{ step_id: \"S1\", status: \"pending\" }],\n };\n\n const payload = buildProjectPayload(form, plan);\n payload.runtime.test_cmd = \"changed\";\n payload.plan.steps[0].status = \"completed\";\n\n assert.deepEqual(form.runtime, { test_cmd: \"pytest -q\" });\n assert.deepEqual(plan.steps, [{ step_id: \"S1\", status: \"pending\" }]);\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"main\",\n origin_url: \"\",\n runtime: {\n test_cmd: \"changed\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"pytest -q\",\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n },\n });\n});\n\ntest(\"buildProjectPayload keeps a manually entered origin URL\", () => {\n const payload = buildProjectPayload({\n project_dir: \"demo\",\n display_name: \"demo\",\n branch: \"main\",\n origin_url: \" https://github.com/openai/demo-app.git \",\n github_mode: \"manual\",\n runtime: {},\n });\n\n assert.equal(payload.origin_url, \"https://github.com/openai/demo-app.git\");\n});\n\ntest(\"buildRunPlanPayloadFromDetail reuses the generated plan and persisted runtime\", () => {\n const detail = {\n project: {\n repo_path: \"C:/work/demo\",\n display_name: \"Demo App\",\n slug: \"demo-app\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n },\n runtime: {\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n };\n\n const payload = buildRunPlanPayloadFromDetail(detail, {\n max_blocks: 7,\n effort: \"high\",\n });\n\n assert.deepEqual(payload, {\n project_dir: \"C:/work/demo\",\n display_name: \"Demo App\",\n branch: \"release\",\n origin_url: \"https://github.com/openai/demo-app\",\n runtime: {\n max_blocks: 7,\n effort: \"high\",\n execution_mode: \"parallel\",\n allow_background_queue: true,\n background_queue_priority: 0,\n model: \"gpt-5.4\",\n test_cmd: \"npm run check\",\n },\n plan: {\n workflow_mode: \"standard\",\n execution_mode: \"parallel\",\n default_test_command: \"npm run check\",\n steps: [{ step_id: \"ST1\", status: \"pending\" }],\n },\n });\n assert.equal(buildRunPlanPayloadFromDetail({ project: { repo_path: \"C:/work/demo\" }, plan: { steps: [] } }, null), null);\n});\n\ntest(\"firstSelectableStepId prefers the first incomplete step\", () => {\n assert.equal(\n firstSelectableStepId({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"running\" },\n { step_id: \"S3\", status: \"pending\" },\n ],\n }),\n \"S2\",\n );\n assert.equal(firstSelectableStepId({ steps: [{ step_id: \"S1\", status: \"completed\" }] }), \"\");\n assert.equal(firstSelectableStepId({ steps: [] }), \"\");\n});\n\ntest(\"planStepsWithCloseout appends a synthetic final closeout node\", () => {\n const steps = planStepsWithCloseout(\n {\n closeout_status: \"not_started\",\n closeout_notes: \"\",\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"pending\" },\n ],\n },\n {\n title: \"Closeout\",\n description: \"Closeout report\",\n successCriteria: \"Closeout report\",\n },\n );\n\n assert.equal(steps.length, 3);\n assert.equal(steps[2].step_id, \"CO1\");\n assert.equal(steps[2].status, \"pending\");\n assert.deepEqual(steps[2].depends_on, [\"ST1\", \"ST2\"]);\n assert.equal(canEditStep(steps[2], false), false);\n});\n\n/*\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"Balanced preset\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"gpt-5.4 | reasoning Low\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []), \"gpt-5.4 | reasoning Auto\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []), \"gpt-5.4 | reasoning Low | /fast\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"gpt-5.4 | reasoning High\");\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), \"gpt-5.4 | 추론 높음\");\n*/\n\ntest(\"runtimeSummary reflects execution mode in preset and direct model summaries\", () => {\n assert.equal(\n runtimeSummary(\n { model_preset: \"balanced\" },\n [{ preset_id: \"balanced\", summary: \"Balanced preset\" }],\n ),\n \"OpenAI/Codex | Standard Mode | Balanced preset | parallel auto\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\", effort: \"low\" }, []), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Auto | parallel auto\",\n );\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"low\", use_fast_mode: true }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning Low | parallel auto | /fast\",\n );\n assert.equal(runtimeSummary({ model: \"gpt-5.4\" }), \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\");\n assert.equal(\n runtimeSummary({ model: \"gpt-5.4\", effort: \"high\", execution_mode: \"parallel\", parallel_worker_mode: \"auto\" }, []),\n \"OpenAI/Codex | Standard Mode | gpt-5.4 | reasoning High | parallel auto\",\n );\n assert.equal(runtimeSummary({}, undefined), \"No model selected\");\n assert.match(runtimeSummary({ model: \"gpt-5.4\", effort: \"high\" }, [], \"ko\"), /^OpenAI\\/Codex \\| .* \\| gpt-5\\.4 .* parallel .*$/);\n});\n\ntest(\"runtimeSummary includes the selected local provider for generic OSS models\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"oss\",\n local_model_provider: \"lmstudio\",\n model: \"local-model\",\n effort: \"medium\",\n },\n [],\n ),\n \"Local/LM Studio | Standard Mode | local-model | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Ollama as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ollama\",\n local_model_provider: \"ollama\",\n model: \"qwen2.5-coder:0.5b\",\n effort: \"medium\",\n },\n [],\n ),\n \"Ollama | Standard Mode | qwen2.5-coder:0.5b | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Gemini CLI as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"gemini\",\n model: \"gemini-2.5-flash\",\n effort: \"medium\",\n },\n [],\n ),\n \"Gemini CLI | Standard Mode | gemini-2.5-flash | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows Claude Code as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"claude\",\n model: CLAUDE_DEFAULT_MODEL,\n effort: \"medium\",\n },\n [],\n ),\n \"Claude Code | Standard Mode | claude-sonnet-4-6 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"runtimeSummary shows the ensemble provider as a first-class backend\", () => {\n assert.equal(\n runtimeSummary(\n {\n model_provider: \"ensemble\",\n model: \"gpt-5.4\",\n effort: \"medium\",\n },\n [],\n ),\n \"GPT+Gemini+Claude Ensemble | Standard Mode | gpt-5.4 | reasoning Medium | parallel auto\",\n );\n});\n\ntest(\"config reasoning helpers keep auto separate from explicit efforts\", () => {\n const modelCatalog = [\n {\n model: \"auto\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"gpt-5.4\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"medium\"],\n },\n ];\n\n assert.deepEqual(configReasoningOptions(modelCatalog, \"auto\", \"medium\"), [\"auto\", \"low\", \"medium\", \"high\", \"xhigh\"]);\n assert.deepEqual(configReasoningOptions(modelCatalog, \"gpt-5.4\", \"medium\"), [\"auto\", \"medium\"]);\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"auto\", effort: \"medium\" }), \"auto\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"auto\", model_preset: \"medium\", effort: \"medium\" }), \"medium\");\n assert.equal(selectedConfigReasoning(modelCatalog, { model: \"gpt-5.4\", effort: \"medium\", effort_selection_mode: \"auto\" }), \"auto\");\n assert.equal(reasoningEffortLabel(\"auto\"), \"Auto\");\n assert.equal(autoRoutingPresetLabel(\"low\"), \"Low Only\");\n assert.equal(autoRoutingPresetLabel(\"xhigh\", \"ko\"), \"매우 높음만\");\n});\n\ntest(\"applyConfigRuntimeModelSelection updates reasoning for models with stricter defaults\", () => {\n const nextRuntime = applyConfigRuntimeModelSelection(\n {\n model_provider: \"deepseek\",\n model: \"deepseek-chat\",\n model_slug_input: \"deepseek-chat\",\n effort: \"low\",\n effort_selection_mode: \"explicit\",\n },\n [\n {\n model: \"deepseek-chat\",\n default_reasoning_effort: \"medium\",\n supported_reasoning_efforts: [\"low\", \"medium\", \"high\", \"xhigh\"],\n },\n {\n model: \"deepseek-reasoner\",\n default_reasoning_effort: \"high\",\n supported_reasoning_efforts: [\"medium\", \"high\", \"xhigh\"],\n },\n ],\n \"deepseek-reasoner\",\n );\n\n assert.equal(nextRuntime.model, \"deepseek-reasoner\");\n assert.equal(nextRuntime.model_slug_input, \"deepseek-reasoner\");\n assert.equal(nextRuntime.effort, \"high\");\n assert.equal(nextRuntime.planning_effort, \"high\");\n assert.equal(nextRuntime.effort_selection_mode, \"auto\");\n});\n\ntest(\"codexUsageBuckets separates 5h, 7d, and spark usage windows\", () => {\n const buckets = codexUsageBuckets({\n rate_limits: {\n default_limit_id: \"codex\",\n items: [\n {\n limit_id: \"codex\",\n primary: { remaining_percent: 70, used_percent: 30, resets_at: \"2026-03-26T05:00:00+00:00\" },\n secondary: { remaining_percent: 55, used_percent: 45, resets_at: \"2026-04-02T00:00:00+00:00\" },\n },\n {\n limit_id: \"codex-spark\",\n primary: { remaining_percent: 20, used_percent: 80, resets_at: \"2026-03-26T02:00:00+00:00\" },\n },\n ],\n },\n });\n\n assert.equal(buckets[0].label, \"5h Usage\");\n assert.equal(buckets[0].window.remaining_percent, 70);\n assert.equal(buckets[1].label, \"7d Usage\");\n assert.equal(buckets[1].window.remaining_percent, 55);\n assert.equal(buckets[2].label, \"Codex Spark\");\n assert.equal(buckets[2].window.remaining_percent, 20);\n});\n\ntest(\"progressCaption summarizes empty, partial, and completed plans\", () => {\n assert.equal(progressCaption({ steps: [] }), \"No plan yet\");\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"pending\" },\n ],\n }),\n \"Completed 1/2 steps, next: S2\",\n );\n assert.equal(\n progressCaption({\n steps: [\n { step_id: \"S1\", status: \"completed\" },\n { step_id: \"S2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/2 steps, closeout running\",\n );\n assert.equal(\n progressCaption({\n steps: [{ step_id: \"S1\", status: \"completed\" }],\n closeout_status: \"failed\",\n }),\n \"Completed 1/1 steps, closeout failed\",\n );\n});\n\ntest(\"toolbarProgressCaptionDisplay shows planning progress while plan generation is active\", () => {\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n planningProgress: {\n stage_count: 4,\n current_stage_index: 2,\n current_stage_status: \"running\",\n },\n },\n ),\n \"Planning stage 2/4, Running\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay(\n { steps: [] },\n \"en\",\n {\n activeJob: {\n status: \"running\",\n command: \"generate-plan\",\n },\n },\n ),\n \"Generating execution plan\",\n );\n});\n\ntest(\"canEditStep only allows pending steps when the controller is idle\", () => {\n assert.equal(canEditStep({ status: \"pending\" }, false), true);\n assert.equal(canEditStep({ status: \"completed\" }, false), false);\n assert.equal(canEditStep({ status: \"pending\" }, true), false);\n assert.equal(canEditStep(null, false), false);\n});\n\ntest(\"commandLabel maps known commands and humanizes unknown ones\", () => {\n assert.equal(commandLabel(\"generate-plan\"), \"Generate Plan\");\n assert.equal(commandLabel(\"run-plan\"), \"Run Remaining Steps\");\n assert.equal(commandLabel(\"run-closeout\"), \"Closeout\");\n assert.equal(commandLabel(\"sync-workspace-now\"), \"sync workspace now\");\n assert.equal(commandLabel(\"\"), \"Background Job\");\n assert.equal(commandLabel(\"run-plan\", \"ko\"), \"남은 단계 실행\");\n});\n\ntest(\"statusTone maps operational states to UI tones\", () => {\n assert.equal(statusTone(\"failed\"), \"danger\");\n assert.equal(statusTone(\"running\"), \"info\");\n assert.equal(statusTone(\"running:debugging\"), \"warning\");\n assert.equal(statusTone(\"running:parallel-debugging\"), \"warning\");\n assert.equal(statusTone(\"integrating\"), \"info\");\n assert.equal(statusTone(\"awaiting_review\"), \"warning\");\n assert.equal(statusTone(\"awaiting_checkpoint_approval\"), \"warning\");\n assert.equal(statusTone(\"cancelled\"), \"neutral\");\n assert.equal(statusTone(\"completed\"), \"success\");\n assert.equal(statusTone(\"paused_for_review\"), \"warning\");\n assert.equal(statusTone(\"pending\"), \"neutral\");\n});\n\ntest(\"effectiveStepStatus overlays debugging on the active running step\", () => {\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:parallel-debugging\"), \"running:debugging\");\n assert.equal(effectiveStepStatus({ status: \"pending\" }, \"running:debugging\"), \"pending\");\n assert.equal(effectiveStepStatus({ status: \"running\" }, \"running:block:2\"), \"running\");\n});\n\ntest(\"shouldShowEstimatedCost only enables paid cost displays when configured\", () => {\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"included\", model_provider: \"openai\" },\n { recent: { billing_mode: \"included\", configured: true }, remaining: { billing_mode: \"included\", configured: true } },\n ),\n false,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"token\", model_provider: \"openrouter\" },\n { recent: { billing_mode: \"token\", configured: true }, remaining: { billing_mode: \"token\", configured: false } },\n ),\n true,\n );\n assert.equal(\n shouldShowEstimatedCost(\n { billing_mode: \"per_pass\", model_provider: \"oss\" },\n { recent: { billing_mode: \"per_pass\", configured: false }, remaining: { billing_mode: \"per_pass\", configured: false } },\n ),\n false,\n );\n});\n\ntest(\"display progress captions include closeout in the visible total\", () => {\n assert.equal(\n executionProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"integrating\", depends_on: [\"ST1\"], owned_paths: [\"desktop/src\"] },\n { step_id: \"ST3\", status: \"running\", depends_on: [\"ST1\"], owned_paths: [\"src/jakal_flow\"] },\n ],\n closeout_status: \"not_started\",\n }),\n \"Completed 1/4 steps, running: ST3; integrating: ST2\",\n );\n assert.equal(\n toolbarProgressCaptionDisplay({\n steps: [\n { step_id: \"ST1\", status: \"completed\" },\n { step_id: \"ST2\", status: \"completed\" },\n ],\n closeout_status: \"running\",\n }),\n \"Completed 2/3 steps, closeout running\",\n );\n});\n"}}}}