在 macOS 与 Windows 上做同一份 Claude Code 与 Codex 状态合同

2026-07-30 针对 Agent Island v1.7.1 核实。公开的稳定版本里包含本文讨论的 macOS 与 Windows 分类器。同一个版本为 Claude 与 Codex 的回合事件准备了各平台的测试样本

Claude 与 Codex 说的不是同一种日志语言

两个工具都追加 JSONL 记录,但它们的信封和完成标记不同。Claude Code 记录助手与用户消息,完成出现在 message.stop_reason 里,取值如 end_turnstop_sequence。Codex 用的是事件记录,负载类型包括 task_startedturn/startedtask_completeturn/completed

跨平台的监控不能把这些服务商细节暴露给 App 的其余部分。如果每一个闹钟、图标和会话列表都各自解释原始事件,那么在第一次格式变动之后,macOS 与 Windows 两个产品就会给出不同的答案

把服务商事件归约成一份很小的合同

Agent Island 把最新的相关记录归约成三个字段:

  • isDone:最近这个回合有没有把控制权交还给用户
  • key:用于闹钟去重的稳定身份
  • activityDate:记录提供的语义事件时间

Swift 那边的类型叫 SessionTurnStatus,Windows 移植用的是同样三个字段的 C# record struct。这条边界之上的一切,都可以在不知道「证据来自 Claude 的助手消息还是 Codex 的事件负载」的情况下,推理一个已结束的回合

从后往前读,停在最新的有意义事件上

分类器逆序扫描有界的记录尾部。这很要紧,因为在一条新的用户消息开启了另一个回合之后,更早的那个完成标记就不再是当前状态。第一个被认出的事件胜出

for line in lines.reversed() {
    guard let event = json(line) else { continue }
    if event["isSidechain"] as? Bool == true { continue }
    // 一找到最新的相关用户或助手事件就返回
}

排除 sidechain 是为了防止一个穿插进来的子 Agent 事件把父对话「完成」掉。这是一条语义规则,不是文件格式上的清理。一条合法的 JSON 记录,仍然可能属于错误的那次对话

一个终止标记仍然可能是错误

Claude Code 可能用与已完成回合相同的助手信封和 stop_sequence 原因,写出一条限流或 API 错误行。这条记录同时带 isApiErrorMessage: true。只看终止原因就判定完成,会产生一次假的「轮到你了」

let isApiError = event["isApiErrorMessage"] as? Bool == true
let isDone = !isApiError
    && ["end_turn", "stop_sequence", "stop"].contains(stopReason)

C# 分类器套用同样的排除。两个平台的单元样本里都有这个用例。跨平台的一致性来自把规则和它的例子一起移植

保住一个稳定的回合键

轮询和文件系统事件可能把同一个已完成回合分类不止一次。因此监控需要一个能熬过又一次扫描的身份。已发布的分类器依次尝试 uuidid,然后是 turn_iditem_idcall_id 这类负载字段。只有在完全没有标识时,才从原始行派生一个有界的兜底

这个键属于语义结果。闹钟代码不该从文件路径或时间戳里另造一套身份方案

移植的是行为,不是语法

macOS 用 Swift,因为产品是一个原生 SwiftUI 菜单栏 App;Windows 用 C# 和 WPF。硬把两边塞进同一个运行时,也免不掉原生的生命周期、文件系统和界面集成

真正值得共享的单元是那张真值表。给定同样的有序证据,两个实现必须给出同样的答案:有没有一个用户事件启动了工作?有没有一个服务商特有的完成标记结束了它?这个标记是不是 sidechain 的一部分?它是不是一个 API 错误?哪个事件键标识这个回合?

这份合同小到可以评审、可以测试。之后各平台的代码就能各自处理观测与呈现,而不会改变「一个回合」的含义

测的是轨迹,不只是辅助函数

好的样本长得像真实记录里见过的失败。把一个更早的完成放在一条更新的用户消息之前;在父事件之后插入一个 sidechain 完成;用一个带 stop_sequence 的 Claude API 错误信封;把 Codex 新旧两套完成事件名都覆盖到

同一批用例在两个移植上都跑一遍。一个新的服务商事件,应该先改语义样本集,再改 Swift 与 C# 的适配层。这样漂移会在评审里暴露,而不是等到用户在不同操作系统上收到不同提醒

这份合同证明不了什么

一个已完成的回合证明不了任务成功了、代码是对的,或者用户应该接受这个结果。它只说明最近一个被认出的记录事件把控制权交还了。产品界面应该守住这条边界

Agent Island v1.7.1 用这些本地证据在 macOS 与 Windows 上显示 Claude Code 与 Codex 的状态和轮到你了提醒。它不把会话数据上传到 Agent Island,也不替用户执行下一步

可以接着读 有界的会话记录监控安全解析 JSONL,或者 完成事件与会话状态

← 全部文章