opencodex 交互式更新提醒(Update Notify Prompt):在 `ocx start` 启动路径上实现三选项升级提示的完整设计、实现与验证
【免费下载链接】opencodexUniversal provider proxy for OpenAI Codex Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code项目地址https://gitcode.com/gh_mirrors/ope/opencodex点击查看免费下载本篇文章基于当前仓库devlog/_fin/260629_update-notify-prompt/下的设计00_design.md、实现10_implementation.md与完成20_completion.md三份文档并结合 src/update/notify.ts、src/update/index.ts、src/cli/index.ts 等实际源码与测试展开完整讲解 opencodex 如何在交互式ocx start时检测新版本并以「Update now / Skip / Skip until next version」三选项提示用户升级。读完本文你可以掌握该功能的启动顺序设计、频道感知版本比较、20 小时后台刷新、静默守护矩阵与可测试性设计并可直接对照源码继续深入。背景与目标opencodex 是一个面向 OpenAI Codex 与 Claude Code 的通用 Provider 代理。在引入本功能之前用户可能连续数周运行旧版本而不自知。参考实现来自 codex-rs原生 Codex CLI的启动提示逻辑opencodex 将其适配到自身基于 npm/bun 的发布方式与既有 src/update/index.ts 升级工具链上。核心目标见 00_design.md在交互式ocx start时若检测到当前频道存在更新的已发布版本在服务器启动前展示一次三选项提示服务/守护进程/非 TTY 运行绝不显示该提示源码检出source checkout运行绝不显示不做未经同意的自动更新不改变既有升级机制本身。用户可见的三选项交互用户在一次交互式ocx start中会看到如下提示文案与实现见 src/update/notify.ts✨ Update available! current - latest Release notes: https://github.com/lidge-jun/opencodex/releases/latest 1) Update now (runs npm install -g bitkyc08/opencodexlatest) 2) Skip 3) Skip until next version [1/2/3] (default 1):三个选项的行为src/update/notify.tsUpdate now1或直接回车执行await runUpdate()完成全局安装随后打印Restart the proxy: ocx start并process.exit(0)。默认回车选中此项与 codex-rs 的高亮默认一致。Skip2本次运行不升级、不改写缓存下次启动继续询问。Skip until next version3把当前latest_version写入dismissed_version该版本不再提示只有出现严格更新的版本才会重新出现。提示中的安装命令字符串由频道决定与runUpdate实际执行的安装命令来自同一来源updateCommand/updateCommandStr见 src/update/index.ts保证标签永不“说谎”。启动顺序提示必须先于端口与 PID 的占用这是本功能最关键的一条实现纪律。ocx start原本的流程是解析参数 →chooseListenPort→startServer→writePid/writeRuntimePort→ 稍后调用maybeShowStarPrompt()旧 src/cli.ts 中的位置。更新提示被插入到handleStart中chooseListenPort/startServer之前见 src/cli/index.ts 附近的await maybeShowUpdatePrompt()。原因在于选择「Update now」会执行一次全局安装然后退出。如果此时服务器已经占用了端口并写入了 PID就会在守护进程持有端口/PID 的情况下覆盖正在运行的全局二进制文件。因此顺序必须是解析参数 - reconcile - (交互式更新提示) - chooseListenPort - startServer同时保留既有的maybeShowStarPrompt()调用src/cli/index.ts位置不动。两个提示通过「星标标记star marker」协调更新提示在星标标记缺失时即用户首次运行直接跳过从而避免在全新安装时两个提示叠加。三个关键设计决策O1/O2/O3设计评审阶段共解决了三个开放问题O1首次运行跳过当星标标记.star-prompted见 src/cli/star-prompt.ts 的hasStarPromptRun()缺失时跳过更新提示从下一次启动再开始评估。因为全新安装通常已经处于最新版本同时避免与一次性星标提示叠加。O2后台刷新复用npm view后台刷新复用update.ts中既有的latestVersion()即npm view pkgtag version12 秒超时失败返回null。通过分离的守护子进程执行其阻塞性不再成为问题。O3preview 频道与稳定版的关系当用户处于 preview 频道时一个基础版本maj.min.pat严格更高的稳定版视为更新并提示让 preview 用户自然回归 stable基础版本相等的稳定版如2.7.0vs2.7.0-preview.5不算更新避免“降级味”的骚扰同基础版本下 preview 与 preview 之间比较末尾的-preview.N。频道感知的版本比较isNewer 的完整语义opencodex 的发布分为两个频道type Channel latest | preview。由于 codex-rs 将任何预发布版本都视为“非更新”若直接照搬preview 用户将永远收不到通知2.7.0-preview.3与2.7.0-preview.5都会解析失败。因此实现拆分了频道各自的比较器src/update/notify.tslatest稳定频道parseStable(v)只接受严格maj.min.pat格式正则^(\d)\.(\d)\.(\d)$任何带后缀的字符串返回null与 codex-rs 一致目标为预发布版本时解析失败一律视为“不更新”——稳定用户永远不会被推到 preview当前版本若为 preview则取其maj.min.pat基础与稳定版比较稳定基础严格更高才更新。preview 频道parsePreview(v)接受maj.min.pat-preview.N四元组正则^(\d)\.(\d)\.(\d)-preview\.(\d)$preview vs preview逐位比较四元组稳定版 vs 当前 preview仅当稳定版基础严格高于preview 基础时视为更新O3其余混合不可解析组合一律返回false。两个频道共用一个元组比较辅助gt(a, b)从左到右逐位比较先出现不同者决定大小缺失位按 0 处理。版本缓存~/.opencodex/version.json缓存文件存放在getConfigDir()默认~/.opencodex可用OPENCODEX_HOME覆盖下的version.json格式在 00_design.md 中定义、在 src/update/notify.ts 中实现{ latest_version: 2.7.0, last_checked_at: RFC3339, dismissed_version: 2.6.9, tag: latest }latest_version最近一次成功查询到的注册表最新版本last_checked_atRFC3339 时间戳记录成功查询时间dismissed_version?用户选择“Skip until next version”时记录的版本tag该缓存所属频道。读写语义readVersionCache(channel)任何解析错误、字段缺失或tag与当前频道不一致稳定↔preview 切换都返回null从而强制重新拉取杜绝跨频道污染src/update/notify.tswriteVersionCache(c)通过atomicWriteFile临时文件重命名见 src/config.ts写入best-effort 失败不影响启动src/update/notify.ts该文件位于用户主目录而非仓库无需 gitignore卸载时uninstall已对整个配置目录做rmSync无需额外清理。20 小时后台刷新detached 子进程 隐藏子命令handleStart作为守护进程会永久阻塞因此进程内的异步拉取会带来句柄/定时器泄漏风险。实现方案是分离的守护子进程triggerBackgroundRefreshIfStale(channel, cache)当缓存缺失或last_checked_at早于 20 小时20 * 60 * 60 * 1000ms与 codex-rs 一致时用process.execPath派生[__refresh-version, channel]参数detached: true、stdio: ignore、windowsHide: true并child.unref()后 fire-and-forgetsrc/update/notify.ts。派生时注入OCX_SERVICE: 1确保辅助进程绝不会弹提示refreshVersionCache(channel)隐藏子命令的主体调用latestVersion(channel)复用 src/update/index.ts 的npm view路径仅在成功时写入新缓存并推进last_checked_at同时保留dismissed_version失败则原样保留缓存让下一次启动重试src/update/notify.ts。该隐藏子命令__refresh-version注册在 CLI 顶层switch中src/cli/index.ts不出现在 help 中只负责写缓存并退出。这一设计的核心哲学是本次运行只读缓存决定是否提示刷新后的值在下一次启动时生效与 codex-rs 相同的非阻塞模型。守护矩阵什么情况下绝对不出现提示shouldConsider()是资格判定入口返回{ channel, current }或nullsrc/update/notify.ts任一条件不满足即静默条件判定依据结果源码检出detectInstall() source或版本为0.0.0isSourceBuildVersion与 codex-rs 的is_source_build_version等价跳过首要门槛服务/非 TTYOCX_SERVICE环境变量存在或 stdin/stdout 非 TTYinteractiveGuardOk通过isatty(0)/isatty(1)判定见 src/update/notify.ts跳过首次运行星标标记缺失hasStarPromptRun()为假O1跳过版本不可读currentVersion()返回?跳过由此形成完整守护矩阵OCX_SERVICE1 ocx start服务、ocx ensure派生的子进程静默ocx start | cat非 TTY静默ocx ensure父进程从不调用该提示handleEnsure未改动保持 autostart 热路径的静默其子进程本身携带OCX_SERVICE1ocx gui派生的 startstdio: ignore→ 非 TTY → 静默源码检出bun run src/cli.ts开发场景静默同时避免每次开发启动都弹模态框首次运行无星标标记静默从下次启动开始评估。注意gui派生的 start 并不携带OCX_SERVICE但!isTTY检查能够捕获它——这正是使用完整三重守卫而非仅检查OCX_SERVICE的原因。执行流程maybeShowUpdatePrompt 的完整调用链src/update/notify.ts 的maybeShowUpdatePrompt()是唯一的入口从handleStart调用。完整流程shouldConsider()判定资格不通过则返回后台刷新逻辑按需触发与否取决于调用方通过资格后读取readVersionCache(channel)触发triggerBackgroundRefreshIfStale(channel, cache)通过getUpgradeVersionForPopup(cache, current, channel)计算要展示的版本缓存存在、isNewer为真、且不是dismissed_version才返回latest_versionsrc/update/notify.ts无可用版本则直接返回否则用readline/promises的createInterface渲染提示try/finally rl.close()保证句柄释放整个函数体包在 try/catch 中永不抛出、绝不阻塞启动按选择执行对应分支见上文三选项行为。整个模块自包含、设计为“永不向启动流程抛异常”这是它能够安全进入启动路径的前提。可测试性设计决策逻辑与 readline 外壳分离提示渲染与process.exit都是副作用因此实现把决策逻辑做成纯函数导出readline 外壳保持极薄。测试位于 tests/update/update-notify.test.ts覆盖latest 频道isNewer2.7.0 2.6.4为真相等为假preview 目标在稳定频道被忽略稳定版对 preview 当前版本按其基础版本比较2.9.1vs2.8.2-preview.…为真2.9.1vs2.9.1-preview.…为假preview 频道isNewer2.7.0-preview.5 2.7.0-preview.3为真相等为假稳定2.8.0vs preview2.7.0-preview.3为真O3 高基础稳定2.7.0vs preview2.7.0-preview.5为假O3 等基础dismiss 抑制与重新浮现dismissed_version latest时不提示严格更新的版本重新提示版本缓存 I/Oround-trip、tag不匹配时readVersionCache返回null、缺失缓存返回null源码构建门isSourceBuildVersion(0.0.0)为真弹出版本计算getUpgradeVersionForPopup的更新/不更新/抑制/重浮现/空缓存五种情况。完成文档20_completion.md记录新增 19 个测试全部通过bun test tests/update-notify.test.ts19 pass / 0 failbun x tsc --noEmit干净全量 1458 pass / 71 fail / 13 errors 与改动前基线完全一致71 fail 13 errors 为既有的无关失败通过 stash 改动集验证确认即本功能未引入任何新失败。验证矩阵与风险控制实现文档 10_implementation.md 给出了验证清单bun x tsc --noEmitbun test tests/update-notify.test.ts手动矩阵OCX_SERVICE1 ocx start静默、ocx start | cat静默、非 TTY、ocx ensure静默、ocx guispawn静默、带桩更新的交互式ocx start显示提示、选 3 后重启保持静默、缓存 bump 到更高版本提示重新出现。风险定级为 STANDARD涉及启动路径缓解手段有四重强制的源码构建/TTY 守卫、整体 try/catch 保证绝不阻塞启动、提示在占用任何端口/PID 之前触发、以及完全不改动既有升级机制。已知边界与后续方向完成文档记录了三个有意保留的行为评审时确认未改代码Enter 默认即“Update now”在纯 readline 场景下误触回车比 TUI 后果更重会全局安装并退出。这是与 codex-rs 的默认高亮对齐的刻意选择提示文案已标明(default 1)若用户反馈误触可再评估对稳定构建强制--tag previewisNewer中表现为静默 no-op 而非错误提示仅在非常规手动参数组合下可达缓存语义按设计本次运行读取的是刷新前的缓存新拉取到的版本在下一次启动才生效codex-rs 同款非阻塞模型O2 替代方案针对无 npm 环境的注册表fetch回退仍保留为可选的后续选项当前实现复用npm view。小结交互式更新提醒是 opencodex 启动路径上一个“小而关键”的功能它把src/update.ts的升级能力、star-prompt.ts的交互守卫经验、config.ts的原子写与配置目录约定整合进一个自包含、永不抛异常的模块。三份 devlog 文档设计 → 实现 → 完成记录了从 codex-rs 参考到 O1/O2/O3 决策收敛再到独立验证通过的完整过程可作为理解该项目“先设计、后实现、再复核”工程方法的范本。对使用者而言它消除了“默默运行旧版本数周”的信息差对贡献者而言src/update/notify.ts、src/update/index.ts 与 tests/update/update-notify.test.ts 是继续扩展该能力例如无 npm 环境回退的起点。赞分享【免费下载链接】opencodexUniversal provider proxy for OpenAI Codex Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code项目地址https://gitcode.com/gh_mirrors/ope/opencodex点击查看免费下载相关推荐Typer CLI 选项交互式提示prompt实战从 promptTrue 到确认提示的完整指南Typer CLI 选项交互式提示prompt实战从 promptTrue 到确认提示的完整指南 CLI 选项Option在参数缺失时默认会直接报错CLIopencodex Windows 更新后的 Codex shim 自动修复ocx update 与 shim repair 的完整机制opencodex Windows 更新后的 Codex shim 自动修复 ocx update 与 shim repair 的完整机制 导读 本文聚焦 oMyIP动画效果实现提升用户体验的微交互设计MyIP动画效果实现提升用户体验的微交互设计 你是否注意到优秀的Web应用总能通过微妙的动画让用户体验更流畅MyIP项目通过精心设计的微交互将原本枯燥的I网络开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考