
oh-my-openagent Windows Team 模式启动与崩溃恢复加固实践从 senpi shim 解析到进程树终止与 liveness 持久化确认【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent导读本文基于仓库中.omo/evidence/omo-senpi-adapter/20260729-windows-team-crash-recovery/README.md这一 Windows 专属验证证据文档系统梳理 oh-my-openagent 在 Windows 平台上对 Team团队模式成员进程启动与崩溃恢复的加固方案。围绕npm 安装的senpishim 如何被正确解析崩溃后如何精确终止进程树liveness 事件如何持久化确认与安全重放三大主题结合packages/omo-senpi与packages/senpi-task的源码实现展开讲解。读完本文你将掌握该补丁的完整改动面、验证门禁设计以及其交付语义的边界at-least-once 与幂等要求。一、背景为什么 Windows 上的 Team 崩溃恢复需要专门加固oh-my-openagent 的omo-senpi包实现了一套团队Team运行模式由 lead session 创建成员member任务、通过 inbox 投递消息、在成员异常退出后进行恢复与 liveness存活通知。这套机制在 macOS/Linux 上表现良好但在 Windows 上有三类典型问题.cmd包装器执行语义不可靠npm 安装的senpi通过senpi.cmd之类的包装脚本暴露 CLI直接交给 Windows 执行时路径解析、退出码传递与信号行为都与原生可执行文件不一致。进程树终止困难Windows 没有 POSIX 的kill(-pgid)语义无法像 Unix 一样用负 PID 杀掉整个进程组同时process.kill(pid, 0)的探测行为也与 Unix 不同Windows 上探测的是单进程而非进程组。崩溃窗口期的交付丢失liveness 通知在sendMessage()返回与持久化确认之间存在时间窗一旦 lead 恰好在窗内崩溃通知可能被确定性丢失。该证据文档记录的补丁正是针对上述问题的专项加固其目标基线为origin/dev的106bf0da14077e42e5e95cdd0ca0c27a86730bda提交。二、补丁核心改动七项加固要点证据文档列出了补丁的七个关键改动下面逐条结合仓库源码展开。2.1 通过process.execPath与 package CLI 解析senpishim补丁的第一个改动是解析 npm 安装的senpishim 时不再让 Windows 直接执行.cmd包装器而是通过process.execPath与包的 CLI 入口来解析。在 packages/omo-senpi/scripts/qa/team-e2e.mjs 的resolveSenpi()中可以印证 Windows 特有的解析顺序const names platform win32 (requested undefined || requested || requested.toLowerCase() senpi) ? [senpi.exe, senpi.cmd, senpi] : [requested || senpi]在 Windows 上优先按senpi.exe→senpi.cmd→senpi的顺序在PATH中查找并且只有当用户显式指定了可执行路径SENPI_BIN环境变量或含路径分隔符的入参时才直接采用其余情况一律走逐目录的PATH扫描platform win32时用;作为路径分隔符、win32.join拼接候选路径。这样的解析保证最终落到的是可以稳定产出一个可执行子进程的入口而不是脆弱的.cmd包装。2.2 对原生可执行文件保留直接 spawn与 2.1 相对的是对于原生可执行文件native executables保留直接 spawn 的方式不做多余的包装层。也就是说加固只针对 npm 包装的 shim 场景避免对已经是原生二进制的入口引入不必要的间接层——这既保留了原生的启动性能也避免了包装脚本带来的退出码/信号失真。2.3 拒绝不安全根 PID任何探测与终止副作用之前先校验补丁要求在任何进程探测probe或终止termination副作用发生之前拒绝非正数、非整数、非有限值non-finite与不安全的根 PID。这一要求在 packages/omo-senpi/src/components/memory/worker/memory-run-supervisor-ic8-process-groups.ts 中有对应的实现export function validateProcessGroupPid(pid: number): number { if (!Number.isInteger(pid) || pid 0) { throw new RangeError(process-group pid must be a positive integer: ${pid}) } return pid }Number.isInteger同时排除了小数与NaN/Infinity等非有限值pid 0排除了 0 与非正数。terminateProcessGroup()与processGroupIsAlive()在进入各自平台分支前都会先经过validateProcessGroupPid确保先验证、后动手。2.4 用taskkill.exe /PID pid /T /F精确终止进程树补丁在 Windows 上改用taskkill.exe /PID pid /T /F终止经过校验的精确进程树/T终止该 PID 及其子进程/F强制终止而非向.cmd包装器发送信号。源码 memory-run-supervisor-ic8-process-groups.ts 中默认运行时的实现为const defaultRuntime: ProcessGroupRuntime { platform: process.platform, runTaskkill: (pid) { const result spawnSync(taskkill, [/pid, String(pid), /T, /F], { stdio: ignore, }) return { status: result.status, signal: result.signal, error: result.error } }, killGroup: (pid) process.kill(-pid, SIGKILL), // 非 Windows 分支 probeGroup: (pid) process.kill(process.platform win32 ? pid : -pid, 0), }可以看到平台分流的整体设计非 Windows 平台走负 PID 的SIGKILL进程组语义Windows 平台则走taskkill.exe。terminateProcessGroup对 Windows 分支会检查taskkill的退出状态非 0 退出码或error都会抛出异常避免静默失败同文件 L40-L57。2.5 在任务记录中持久化notification.liveness_notified_epoch补丁将 liveness 通知的确认状态持久化进任务记录notification.liveness_notified_epoch写入 task records。该字段在 packages/senpi-task/src/state/types.ts 的TaskNotification类型中定义export type TaskNotification { readonly run_epoch: number readonly notified_epoch: number readonly notification_failed_epoch?: number readonly liveness_notified_epoch?: number }它记录的是liveness 通知已在哪个 run epoch 被确认投递与普通notified_epoch区分开是崩溃恢复后判断这条 liveness 是否还需要重放的权威依据。2.6 仅在 delivery key 持久可观察后才确认 restart-liveness更关键的是确认时序只有匹配的 delivery key 在 lead session 的 JSONL 中持久可观察durably observable之后才承认 restart-liveness 已确认。packages/omo-senpi/src/components/task/member-liveness.ts 实现了这套机制delivery key 的命名空间为team-member-liveness:task_id:run_epochDELIVERY_KEY_PREFIXrecord.task_idrecord.notification.run_epoch每次notifyTerminal(record)先检查wasDelivered与内存delivered集合避免重复投递投递后进入pendingAcknowledgement待确认表acknowledgePersisted(sessionFile)通过createIncrementalSessionMarkerIndex(livenessDeliveryKeysFromSessionText)增量解析 lead session 的 JSONL逐行扫描custom_message含omo-senpi:wake内嵌的 liveness只有当 delivery key 真实出现在持久化的 JSONL 文本中才调用acknowledge()完成确认并清除待确认记录。也就是说发送成功不等于已确认确认的唯一依据是 lead session 磁盘上的 JSONL 中出现了携带该 delivery key 的消息。2.7 结构化校验 会话级 fencing 崩溃后重放补丁还做了三件事校验结构化 liveness 事件livenessDeliveryKeys()/livenessDeliveryKeysFromSessionText()只接受customType senpi-task.team-member-liveness且details.deliveryKey以DELIVERY_KEY_PREFIX开头的负载member-liveness.ts L156-L176无关事件负载不会被当作 liveness 接受。会话级 fencingreplay 与实时通知都被围栏fence到所属的 lead session避免跨会话的 liveness 串扰。lead 重启后重放未确认的 liveness当 lead 在投递成功但未持久确认的窗口内崩溃并重启未确认的 liveness 事件会在恢复后被重放。三、验证体系从聚焦测试到全量门禁对比证据文档记录了完整的验证路径这是理解该补丁可信度的关键部分。3.1 历史聚焦验证已被新流程取代补丁评审前的候选版本曾在七个聚焦测试文件上跑出83 passed / 0 failed、226 条断言同时通过packages/omo-senpi与packages/senpi-task的 typecheck、Team QA 自检、源码新增行的安全扫描未发现凭证赋值、shell: true、动态执行、按镜像名杀进程、pkill/killall等风险以及git diff --check。需要特别说明的是这份历史数据属于评审前候选版本文档明确标注superseded已被取代。由于验证主机是 macOS无法执行 Windows 专属的实时路径因此该文档提交的 JSON 证据早于强化后的 harness 字段不得作为修订后 crash gate 的证据引用——这是一个文档级别的诚实性约束只有重新跑一次全新的 Windows 运行才能声称那些新字段有效。3.2 全量上游门禁对比baseline vs candidate由于 Windows 主机无法创建跨平台测试所需的符号链接且未隔离 home 目录的测试会暴露既有的 user-agent/配置发现行为验证团队采用同一主机上构建并测试干净的origin/devworktree来区分基线失败与补丁回归门禁干净的origin/dev候选补丁结论bun run typecheck无需对比exit 0PASSbun run buildexit 0exit 0PASSbun test12,267 pass / 73 fail / 22 skip12,287 pass / 73 fail / 22 skip失败数无增加新增 20 个通过用例bun run test:codex359 pass / 28 fail / 7 skip359 pass / 28 fail / 7 skip与基线完全一致剩余的 root 与 Codex 失败均为该 Windows 主机上的既有问题主要来源是符号链接 fixture 的EPERM、checkout 符号链接物化、home 目录污染以及与超时相关的敏感扫描。3.3 历史 Windows Team 实时 E2Enode packages/omo-senpi/scripts/qa/team-e2e.mjs曾针对早期候选版本跑过一次 Windows 实时 E2E使用隔离的 mock 凭证与状态结果为PASS、13/13 项检查全 true关键观察包括崩溃发生在成员注入之后、reservation 提交之前精确的成员与父进程树被终止重启干净退出lead liveness 在重启后被注入观察到的 liveness 投递为当前 run epoch 持久化了liveness_notified_epoch真实 Senpi coding-agent 目录保持不变凭证隔离保持干净泄漏子进程 PID 为 0。但文档同样强调该 E2E 的 13 项检查不包含修订后的 alive-before-kill 顺序、精确的 reserved-message 替换投递、以及持久化 JSONL 确认字段因此仅作为历史上下文保留未被手工改写以伪装成强化 harness 的输出。如果你要复跑这套 QA可以在仓库中查看 team-e2e.mjs、team-e2e-crash.mjs 与 team-e2e-support.mjs。崩溃恢复场景的检查项在evaluateCrashRecovery()中定义team-e2e-crash.mjs L131-L163它逐项断言崩溃发生在 reservation 未提交时、restart 后 reservation 恢复为 unread、reserved 消息恰好被投递一次eventCount 1 envelopeCount 1、restart 干净退出、liveness 被注入且liveness_notified_epoch run_epoch——这正是 2.5 与 2.6 两项改动的验收标准。四、隔离与安全要求证据文档对验证过程提出了严格的隔离要求这些要求同样约束任何复现者不提交任何 API key、OAuth token、认证头、环境变量转储、私有提示词或会话转录结构化证据中移除本地绝对路径、主机用户名、一次性 UUID 与进程 ID验证期间不修改当前已安装的 Senpi 插件。这保证了证据文件可以被安全地归档进仓库例如本证据目录中的team-e2e.json与crash-recovery.json同时不泄露任何环境敏感信息。五、残余交付语义at-least-once 与幂等要求文档最后给出了一个重要的工程结论——liveness 通知在事件投递与持久化确认之间的崩溃场景下仍然是 at-least-once至少一次语义补丁关闭了sendMessage()返回之后的确定性丢失路径因为确认依据从发送完成改为JSONL 持久可观察但一个足够窄的崩溃窗口仍然可能产生重复重放duplicate replay事件已投递、确认尚未持久化时 lead 崩溃重启后重放就会再投一次因此下游处理必须保持幂等idempotent。这与 2.6 的 delivery key 设计呼应接收方通过deliveryKeyteam-member-liveness:task_id:run_epoch即可识别重复投递从而安全地幂等消费。六、小结可复用的加固模式该证据文档背后是一套可复用的跨平台进程治理 崩溃恢复交付模式归纳如下平台差异显式化用platform分流的ProcessGroupRuntime抽象runTaskkill/killGroup/probeGroup把 Windows 的taskkill.exe /PID pid /T /F与 Unix 的kill(-pid, SIGKILL)隔离到同一接口两侧先验证后副作用任何 PID 在探测/终止前必须通过正整数 整数 有限值校验杜绝非安全 PID 造成误杀确认以持久化为准内存态delivered只是软去重真正的确认标志是 lead session JSONL 中可观察的 delivery key验收即断言QA 脚本的evaluateCrashRecovery把每条加固语义写成可断言的检查项并把失败数不增加作为相对基线的回归判据诚实标注证据边界历史 Windows 证据与强化 harness 的字段不混用未重新运行前不得声称新版字段有效。对于在 Windows 上运行 oh-my-openagent Team 模式的开发者或正在设计类似跨平台进程监督与至少一次交付机制的工程师这份证据文档与其背后的源码process-group 实现、member-liveness 实现、TaskNotification 类型是一份完整的、可检验的参考实现。【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考