oh-my-codex 0.21.2 发布说明解析:macOS 原生运行时水合、GitGuardex HUD 进度与印尼语本地化
本篇文章以 oh-my-codex(OmX)0.21.2 补丁版本为主线,围绕三大核心变更展开:macOS arm64 上 omx-runtime 原生二进制的“水合(hydration)”机制、可选的 GitGuardex 分支收尾进度在 HUD 中的实时展示,以及覆盖全部本地化 README 的语言导航同步。读者读完本文后将掌握该版本的安装/更新行为变化、HUD 配置方法、事件流数据格式,以及对应的源码实现位置与测试验证路径。
版本概览与定位
oh-my-codex 是一个为 Codex CLI 提供 hooks、Agent 团队、HUD 等扩展能力的开源项目。0.21.2 是一个补丁(patch)版本,发布于 2026-09-01,覆盖 v0.21.1..04533ebfc887643586e37180ec3270473948115a 这一提交区间:共 11 个提交、29 个变更文件(净增 +1,245 行、净删 −10 行),并合并了三个 PR(#3599、#3601、#3602)。
该版本的可复现范围记录在 artifacts/release-0.21.2/inventory.md,官方发布说明见 docs/release-notes-0.21.2.md,同一内容的发布正文模板位于 RELEASE_BODY.md。
三大 Highlights
- macOS arm64 原生运行时水合(#3602):全局安装与同版本重装时,会把
omx-runtime水合进经过校验的原生缓存;即时更新与延迟更新路径也会在脚本抑制安装后执行水合,且网络行为有界、非致命。 - GitGuardex 收尾进度进入 HUD(#3601):可选的、项目级 HUD 集成,实时展示 review/autofix 收尾进度;支持从嵌套 worktree 路径解析配置,在 stat 调用前限制元数据读取量,并使用适配一秒 HUD 观察间隔的自旋动画节奏。
- 印尼语 README(#3599):新增 Bahasa Indonesia 翻译,并同步了所有本地化 README 的语言导航。
兼容性说明
- 补丁版本,无破坏性 API 变更;
- GitGuardex 集成默认关闭,需要显式进行项目级 HUD 配置才会启用;
- 运行时水合保持“fail-closed”的校验和/缓存验证语义——即校验失败时宁可关闭对应功能,也不会放任未经验证的二进制进入缓存;
- 当资产或网络不可用时,包安装不会因此失败(水合失败非致命)。
macOS arm64 原生运行时水合机制
为什么需要“水合”
oh-my-codex 的某些运行时能力由 Rust crate 提供(见 Cargo.toml 中的 omx-runtime、omx-sparkshell、omx-mux、omx-api、omx-explore、omx-runtime-core 等)。这些原生二进制需要针对具体平台架构分发。在 0.21.2 之前,macOS arm64 环境在“全局安装”和“同版本重装”场景下可能存在运行时未就绪的问题;0.21.2 补上了这两条路径的“水合”动作。
水合的入口与调用链
核心函数是 hydrateOmxRuntimeNonFatal,定义于 src/scripts/postinstall.ts:
export async function hydrateOmxRuntimeNonFatal(options: {
packageRoot?: string;
env?: NodeJS.ProcessEnv;
log?: (message: string) => void;
timeoutMs?: number;
} = {}): Promise<string | undefined> {
const log = options.log ?? ((message: string) => console.log(message));
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? RUNTIME_HYDRATION_TIMEOUT_MS);
timer.unref?.();
try {
const runtimePath = await hydrateNativeBinary("omx-runtime", {
packageRoot: options.packageRoot,
env: options.env,
signal: controller.signal,
});
if (runtimePath) log(`[omx] Hydrated omx-runtime for this platform: ${runtimePath}`);
else log("[omx] omx-runtime was not available for this platform; native runtime features remain disabled.");
return runtimePath;
} catch (error) {
log(`[omx] omx-runtime hydration failed non-fatally: ${error instanceof Error ? error.message : String(error)}`);
return undefined;
} finally {
clearTimeout(timer);
}
}
关键点:
- 非致命语义:整个水合过程被
try/catch包裹,失败时仅打印omx-runtime hydration failed non-fatally: ...并返回undefined,不会让安装/更新流程崩溃; - 超时保护:默认超时
RUNTIME_HYDRATION_TIMEOUT_MS = 15_000(15 秒),通过AbortController中止底层下载,timer.unref()保证不会阻塞进程退出; - 平台不可用兜底:若该平台没有对应的
omx-runtime资产,会输出“native runtime features remain disabled”的提示并继续; - 底层实际下载/校验逻辑由 src/cli/native-assets.ts 的
hydrateNativeBinary完成,配合 src/native-assets/policy.ts 的策略(校验和验证、缓存位置管理)。
更新路径如何接入水合
更新流程在 src/cli/update.ts 中把水合作为依赖注入:
interface UpdateDependencies {
...
hydrateRuntime: (ownership?: PackageManagerOwnership) => Promise<string | undefined>;
...
}
其默认实现(src/cli/update.ts)为:
hydrateRuntime: (ownership) => hydrateOmxRuntimeNonFatal({
packageRoot: ownership?.packageRoot,
env: ownership?.environment,
}),
并且更新后的安装时间戳写入逻辑(writeSuccessfulInstallStamp,src/cli/update.ts)会记录 installed_version、install_channel、install_source、install_revision、dev_base_version、package_manager 等元数据。这意味着“即时更新”和“延迟更新”在脚本(postinstall)被抑制的安装场景下,仍会由更新流程主动执行一次水合。
水合的行为边界小结
| 场景 | 行为 |
|---|---|
| 全局安装 | 安装后水合 omx-runtime 到经校验的原生缓存 |
| 同版本重装 | 同样执行水合(noop-same-version 之外仍补水合动作) |
| 即时更新(immediate update) | 更新脚本安装后调用 hydrateRuntime 水合 |
| 延迟更新(deferred update) | 后台任务中同样水合 |
| 校验和失败 / 网络不可用 | 非致命,功能禁用,安装不失败 |
相关的回归测试位于 src/scripts/tests/postinstall.test.ts 与 src/cli/tests/native-assets.test.ts,可据此验证“fail-closed 校验 + 非致命失败”的组合行为。
GitGuardex 收尾进度集成到 HUD
背景:HUD 是什么
omx hud --watch 是 OmX 的监控/状态界面(README 明确说明它是“monitoring/status surface, not the primary user workflow”,见 README.md)。它在一个可定制窗口中聚合展示版本、Git 分支、ralph、ultragoal、team、autopilot 等状态。0.21.2 给这个面板增加了一个可选的 GitGuardex(gx)收尾进度行。
配置方式(关键实操)
GitGuardex 集成默认关闭,且只读项目级配置。需要在项目的 .omx/hud-config.json 中显式开启:
{
"preset": "focused",
"git": { "display": "repo-branch" },
"statusLine": { "preset": "focused" },
"guardex": { "enabled": true }
}
- 当
"guardex": { "enabled": true }存在时,HUD 会显示形如gx:<step>/<total> <phase>的进度;review/autofix 阶段运行时会有自旋动画; - 当该键缺失或为
false时,OmX 完全不读取 Guardex 状态(见 README.md); - 即使没有配置 Guardex,HUD 其余部分照常渲染——该集成对未启用者零影响。
配置结构定义在 src/hud/types.ts:
export interface HudGuardexConfig {
/** Read repository-local `gx branch finish` progress into the HUD. */
enabled?: boolean;
}
export interface HudConfig {
preset?: HudPreset;
git?: HudGitConfig;
statusLine?: HudStatusLineConfig;
guardex?: HudGuardexConfig;
}
export const DEFAULT_HUD_CONFIG: ResolvedHudConfig = {
preset: 'focused',
git: { display: 'repo-branch' },
statusLine: { preset: 'focused' },
guardex: { enabled: false }, // ← 默认关闭
};
相关测试 src/hud/tests/types.test.ts 中有两条关键断言:“keeps GitGuardex integration disabled by default”与“enables GitGuardex progress only when explicitly configured”,正好对应上述默认关闭、显式开启的行为。
事件流数据格式与解析规则
Guardex 把收尾进度写入仓库本地状态目录 .omx/state/finish-runs/(独立于 OMX 会话/团队状态根目录,见 src/hud/state.ts)。每个事件文件是 JSONL(每行一个 JSON 事件),文件名形如 finish-<runIdBase36>-<...>.jsonl。
RawGuardexFinishEvent 的字段(src/hud/state.ts):
interface RawGuardexFinishEvent {
schemaVersion?: unknown;
runId?: unknown;
timestamp?: unknown;
stage?: unknown; // 如 review / autofix / finish
state?: unknown; // 如 running / pending / failed / finished
index?: unknown; // 当前步骤序号(从 1 开始)
total?: unknown; // 总步骤数
label?: unknown;
}
normalizeGuardexFinishEvent(src/hud/state.ts)的过滤规则非常严格,任何一条不满足即返回 null(不展示):
schemaVersion必须等于1;runId、timestamp、stage、state、label必须非空;stage不能是finish,state不能是pending;index与total必须是安全整数且0 < index <= total;runId需匹配^finish-(\d+)-,从中解析出 PID,且该进程必须存活(process.kill(pid, 0)探活);timestamp必须是合法时间,且事件年龄不超过GUARDEX_FINISH_MAX_AGE_MS = 24h;state处于终态集合{failed, finished}时直接判为无效(收尾已结束,不再展示);- 输出字段会被截断:
stage/state最多 40 字符、label最多 80 字符。
解析后对外暴露的 HUD 结构 GuardexFinishStateForHud(src/hud/types.ts):
export interface GuardexFinishStateForHud {
active: true;
stage: string;
state: string;
index: number;
total: number;
label: string;
updatedAt: string;
}
读取策略:先限定再 stat,保证每 tick O(1)
readGuardexFinishState(src/hud/state.ts)采用了刻意设计的读取策略,避免拖慢 HUD 的一秒观察周期:
- 用
findGitLayout(cwd)?.worktreeRoot解析嵌套 worktree 场景下的仓库根(这正是 PR 说明中“resolves configuration from nested worktree paths”的落地); - 列出
.omx/state/finish-runs/下的文件,用正则^finish-([0-9a-z]+)-\d+-[^/]+\.jsonl$过滤,并把文件名中的 base36 编码的运行起始时间解析出来(Number.parseInt(match[1], 36)); - 按起始时间降序只保留最新的
GUARDEX_FINISH_MAX_CANDIDATES个候选——在 stat 调用之前就完成数量裁剪,保证一次 HUD tick 的元数据读取有界; - 对候选做
stat取mtimeMs,按修改时间降序,逐个读文件尾部(readFileTail只读最后GUARDEX_FINISH_TAIL_BYTES字节)取最后一条合法事件; - 读到第一条有效状态即返回;文件不可读/不存在/解析失败全部 fail-open,仓库没有 Guardex 状态目录时 HUD 正常渲染。
其中 readFileTail(src/hud/state.ts)先 stat 得到文件大小,再取 min(size, TAIL_BYTES) 从文件末尾偏移处读取,避免把大文件整体读入内存。解析时对“写入方正在追加最后一行 JSON”的竞态也做了容错(catch {} 忽略半行)。
渲染:进度文本与自旋动画
渲染逻辑在 src/hud/render.ts:
const GUARDEX_SPINNER_FRAMES = ['◐', '◓', '◑', '◒'] as const;
const GUARDEX_SPINNER_FRAME_MS = 400;
function renderGuardexFinish(ctx: HudRenderContext): string | null {
if (!ctx.guardexFinish?.active) return null;
const { index, total, state } = ctx.guardexFinish;
if (!Number.isSafeInteger(index) || !Number.isSafeInteger(total) || index <= 0 || total <= 0 || index > total) return null;
const stage = sanitizeDynamicText(ctx.guardexFinish.stage).slice(0, 40);
if (!stage) return null;
const animates = state === 'running' && (stage === 'review' || stage === 'autofix');
const frame = animates
? ` ${GUARDEX_SPINNER_FRAMES[Math.floor(Date.now() / GUARDEX_SPINNER_FRAME_MS) % GUARDEX_SPINNER_FRAMES.length]}`
: '';
return yellow(`gx:${index}/${total} ${stage}${frame}`);
}
要点:
- 输出格式为
gx:<index>/<total> <stage>,例如gx:2/5 review; - 只有
state === 'running'且stage为review或autofix时才附加自旋动画帧; - 自旋帧切换周期
GUARDEX_SPINNER_FRAME_MS = 400ms,四个帧◐ ◓ ◑ ◒循环。HUD 的观察间隔为一秒,400ms 帧周期保证动画推进节奏稳定、不会在一个 tick 内跳跃式快进——这正是 PR 说明中“spinner cadence advances under the one-second HUD watch interval”的含义; sanitizeDynamicText对动态内容做清洗后再截断,避免把 Guardex 文件中的原始文本直接写入终端。
数据流总结
GitGuardex (gx branch finish)
↓ 写入 JSONL 事件
.omx/state/finish-runs/finish-<base36>-<...>.jsonl
↓ omx hud 每 tick 读取(限候选数 → stat → 读尾部 → 校验/归一化)
GuardexFinishStateForHud
↓ renderGuardexFinish
HUD 窗口:gx:2/5 review ◑
相关测试见 src/hud/tests/state.test.ts(覆盖事件解析、终态忽略、年龄过滤、PID 存活判定、依赖注入的 statFile 模拟)与 src/hud/tests/render.test.ts(覆盖 gx: 渲染与动画)。
印尼语 README 与本地化导航同步
#3599 新增了 Bahasa Indonesia 翻译(docs/readme/README.id.md),并同步了所有本地化 README 的语言导航。目前仓库 docs/readme/ 下已存在 16 种语言版本(de、el、es、fr、id、it、ja、ko、pl、pt、ru、tr、uk、vi、zh-TW、zh 及英文主 README),所有语言的导航区块保持一致,便于读者在各语言版本之间切换。这属于纯文档变更,不涉及运行时行为。
常见问题与使用建议
Q:升级到 0.21.2 后什么都没配,为什么 HUD 没有 gx: 行?
A:这是预期行为。GitGuardex 集成默认关闭(guardex.enabled === false),且只有项目级 .omx/hud-config.json 显式开启后 OmX 才会读取 Guardex 状态。
Q:水合失败会影响安装吗?
A:不会。hydrateOmxRuntimeNonFatal 对任何失败都只打印非致命日志并返回 undefined;安装/更新照常完成,仅原生运行时功能保持禁用。
Q:Guardex 状态目录不存在时会怎样?
A:HUD 正常渲染,readGuardexFinishState 捕获目录缺失异常并返回 null,不会影响其他状态行。
Q:如何验证我的环境是否成功水合?
A:安装/更新日志中会出现 [omx] Hydrated omx-runtime for this platform: <path>;若平台无可用资产,则出现“native runtime features remain disabled”提示。
Q:这些变更与 0.21.1 相比有破坏性吗? A:没有。0.21.2 是补丁版本,两项功能变更均为新增能力且默认不改变既有行为(水合补全了安装/更新路径,Guardex 集成默认关闭)。
结语
0.21.2 是一个“小而稳”的补丁版本:它补全了 macOS arm64 上原生运行时在安装/更新路径中的水合闭环,为 HUD 增加了可选、有界、fail-open 的 GitGuardex 收尾进度展示,并完成了印尼语本地化。其实现处处体现工程上的克制——水合非致命、校验 fail-closed、Guardex 默认关闭、读取候选在 stat 前裁剪、动画帧周期适配观察间隔——这些细节都值得在 src/scripts/postinstall.ts、src/cli/update.ts、src/hud/state.ts 与 src/hud/render.ts 中逐一对照阅读。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00