关于 afk
afk 是供 Firstmate 团队负责人暂时离开时使用的技能。它会记录离开期间的指示和返回条件,并保留权限边界与返岗简报流程。
主要功能
- 01记录离开指示与返回信息
- 02将不确定决策留待负责人处理
- 03支持离开期间的监督会话
- 04按序生成返岗简报
适用场景
- 01暂离时继续监督团队日常工作
- 02延后需要负责人决定的事项
- 03返回后查看团队结果与阻塞项
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ kunchenguid/ firstmate --skill afk安装后在 Claude Code 中输入/afk确认已加载。
详细介绍
afk
离开模式是单次监督会话的一种姿态,而不是第二种架构。
离开只会改变两件事:如何通知 captain,以及遇到由 captain 决策的节点时如何处理(等待其返回,或采用 captain 的离开前话语中已经给出的答案)。
它绝不会改变权限集合。
该姿态记录在文件 state/.afk-contract 中,且只能由 bin/fm-afk-contract.sh 在执行 /afk 的同一轮写入;不会根据聊天推断该姿态。
带有静默模式的记录(bin/fm-afk-contract.sh mode)并非此姿态:此时 captain 在场,因此本技能中所有等待其返回的处理都不适用(由 quiet 技能负责)。
输入 /afk 本身就是同意:captain 之后可能不会再看屏幕,因此进入离开模式时绝不会等待进一步的人工回复,也不会通过回读来设关卡或征求同意。
等待返回是默认且本版本唯一记录的联系方案:没有电话渠道,且每次进入时都会明确说明这一点。
进入:/afk [words]
- 先写入记录,再在同一轮中做其他事。
在进行任何其他工作之前,运行
bin/fm-afk-launch.sh enter --words-file <path> [--expected-return <UTC ISO 8601>] [--spend <n>](或--words <text>)。 它会立即写入state/.afk-contract,无需单独确认,然后打印进入公告和记录的回读内容。 这些文字就是完整的授权内容:bin/fm-afk-contract.sh会按原样记录,不设条款字段、动词、ID 或合并授权列表;根据 captain 的授权,任何解析器、分词器、分类器或语法都不会在任何地方读取这些文字。 请阅读bin/fm-afk-contract.sh --help查看可用标志,不要靠记忆使用。 不带文字的纯/afk是有效的进入方式,但不包含授权内容;公告会说明没有记录任何指示。 已经离开时再次调用不带新文字的/afk,属于刷新,不会改动现有记录;新文字会立即替换授权内容,保留原始会话进入记录,并将被取代的文字存档,以供返回简报使用。 - 记录写入后,按运行环境分别处理:
- Pi 和 pi-signed:无需启动任何内容;直接进入公告。
Pi 上不再启动 away daemon;普通监督会话(
docs/pi-supervision-branch.md)会在记录存在时继续运行,且bin/fm-afk-launch.sh start在这些运行环境中会拒绝启动。 记录存在时,main 会暂停:监督分支会处理所有安全且可执行的唤醒,captain 的处理结果会累积到返回简报中,main 的常设权限会通过受保护的脚本转移到该分支(docs/pi-supervision-branch.md「Postures」);只有分支拒绝处理的唤醒(包括分支故障或扫描不安全)或监视器故障才会唤醒 main。 Pi 上的/quiet无需额外操作:有人值守的分支本就会让日常唤醒不进入此对话,因此在记录存在期间保持安静,就是该有人值守姿态本身的特性。 - 运行监督主机的 home(Claude home,除非
config/supervision-host-off将其停用;或设置了config/supervision-host且未停用的 Cursor、OpenCode、omp、Grok 或 Codex home;见docs/configuration.md「Supervision host」):执行/afk时无需启动任何内容;直接进入公告。 该处的 away session 是监督主机(docs/supervision-host.md):main 暂停期间,它会在记录约束下通过无头引擎运行分支的授权内容;bin/fm-afk-launch.sh start和start-native会拒绝在该 home 上启动 away daemon。 如果enter打印了Supervision host: no engine ...这一行,则每次 away 唤醒都会转到此对话;请在公告中说明。 在有人值守主机运行的环境中,/quiet不会进入任何状态;其他环境中仍会启动下文所述的 daemon(由 quiet 技能的quiet-check决定)。 - 具有原生的窗格内跟踪后台工具的运行环境(claude 和 grok,且 home 未运行监督主机):运行
bin/fm-afk-launch.sh start-native,然后通过该原生工具运行FM_AFK_STATE_PREPARED=1 bin/fm-afk-start.sh。 这是有意不使用单独终端的例外,因为由运行环境托管的作业不会创建终端或改变布局,而 shell 启动器无法调用运行环境原生的后台工具。 如果原生启动失败,运行bin/fm-afk-launch.sh stop回滚已准备好的生命周期。 不要将其包装在nohup ... &中(Codex/herdr 可能会在工具调用返回后回收通过 shell 启动的脱离子进程)。 - 其他所有运行环境(codex、opencode、omp,以及 home 未运行监督主机的 cursor,还有 kimi):运行
bin/fm-afk-launch.sh start。 它是 daemon 终端的唯一所有者:在窗格后端上,它会创建一个不可见的受跟踪终端,并将 captain 端点作为FM_SUPERVISOR_TARGET传入,这样 daemon 会向 captain 注入内容,而不是注入到自己的新窗格中(docs/herdr-backend.md「Away-mode supervisor support」)。 在 t3code 后端上,没有可创建的终端,因此start会拒绝启动,只有上面的原生路径可用;由 T3 Code 托管的 Codex captain 没有 away daemon(docs/t3code-backend.md「Active limits」)。 两种 daemon 路径都要求存在由enter写入的记录,并共用bin/fm-afk-start.sh作为 daemon 入口。 daemon 受存在状态控制:只有在state/.afk存在时才会注入升级事项,否则会保持安静。
- Pi 和 pi-signed:无需启动任何内容;直接进入公告。
Pi 上不再启动 away daemon;普通监督会话(
- 先公告,再在进入后回读。
传达公告的核心意思:仅暂缓处理并等候返回,不通过电话联系;你的指示已经记录,away session 会在能力范围内执行;任何不确定的事项或需要你处理的事项都会等你回来;无论文字如何表述,都不能预先授权破坏性、不可逆或涉及安全的操作。
然后使用
AGENTS.md第 9 节的措辞,用普通句子逐句复述你对这些文字的理解——说明它们要求做什么,不要使用编号字段列表;同时说明预计返回时间、支出上限,以及一句话的联系公告。 明确说明有哪些句子(如果有)无法在你离开期间执行,例如需要红色合并、丢弃内容或任何位于 never-set、仅限本地的落地操作;这些都要等你回来。 此回读仅供参考:记录已经生效,因此不要征求同意或等待回复;如果 captain 希望采用不同的理解,可再次发送带有新文字的/afk。 - 在 daemon 运行的地方,不要另外启动
fm-watch.sh。 daemon 会将监视器作为其子进程管理;单实例锁会无害地忽略意外启动的实例。 Pi 上的启动方式不变:监督会话自身的循环会继续运行。
离开期间
- 记录已经存在,因此无论采用哪种监督形式,watcher 都不会重新检查为负责人保留的事项;返回摘要会列出这些事项。
已声明的外部等待仍按条件感知的数小时级节奏重新检查(
bin/fm-watch.sh、bin/fm-classify-lib.sh)。 - 离开期间的会话会依据负责人的话行事。
它会在每次唤醒结束时读取这些话,根据自己的判断决定眼前的事件是否正是负责人所指的时机;只会在既有授权范围内通过受保护的脚本执行操作,绝不类比行事;有疑问时会以 captain 为判定结果并暂缓处理;对于依据这些话采取的操作,每份结果摘要都会以“根据您离开前的指示:”开头(
bin/fm-branch-prompt.sh中的“姿态”规定了执行规则)。 无论指示如何,破坏性、不可逆和涉及安全敏感的操作都绝不会预先授权;除非这些话已明确预先回答具体决策,否则 ask-user 结果仍遵循ask-user-authority策略;其他需要负责人决策的事项都会留待其返回后处理。 - 在 Pi 上,main 处于停放状态,监督分支会在 main 的既有授权范围内,通过 main 也会使用的同一组受保护脚本,处理每次安全且可执行的唤醒:只要某个拉取请求在其当前 head 上通过检查,就可以合并(具体指哪个请求由该分支解读负责人指示);阻塞条件已解除的排队工作——无论是已在队列中的工作,还是分支因指示明确要求而提交的工作——都会在支出上限内派发;决策则依据负责人预先给出的答案,或按照
ask-user-authority处理。 其他事项都会留待负责人返回后处理;离开期间绝不会继续合并检查未通过的请求;本地独占的落地操作始终等待负责人;只有分支拒绝处理的唤醒(包括分支损坏或扫描不安全)或 watcher 故障才会唤醒 main(docs/pi-supervision-branch.md中的“姿态”)。 - 在运行监督主机的非 Pi 家用环境中,主机的引擎会在相同规则下充当该分支;它交回的唤醒会通过对应框架自身的唤醒路径到达 main(Claude 中的
Stop hook feedback、Cursor、OpenCode 和 omp 中的watcher后续通知、Grok 中 arm 的后台任务完成通知、Codex 中 checkpoint 的输出),并带有一行supervision-host::这是自动监督,不代表负责人已经返回,因此应按照离开期间的姿态处理(监督协议)。 - 会话启动摘要会在 AFK 小节中报告当前姿态,因此重启后会根据记录恢复姿态,而不是依赖记忆。
如何退出:返回
无需使用 /back。第一条真正的消息就是返回信号:
- 如果一条消息不属于下方任何内部形式,且不是以
/afk开头 -> 船长已返回。 在处理这条让船长返回的消息之前,先运行bin/fm-afk-return.sh。 如果运行过守护进程,该脚本负责按正确顺序关闭守护进程、归档姿态记录、持久化呈现唤醒信息并在处理后确认、记录升级和卡死证据、生成返回简报,以及执行返回追赶门禁。 按生成顺序转达返回简报的所有部分,并使用第 9 节的语言;该顺序由bin/fm-afk-return.sh负责。 只要某个blocked:事件仍未关闭,门禁就会将其保留,直到有证据证明其阻塞方已解决:立即通过常规生命周期逐一处理,或以持久化原因明确重新分类,并使用resolved [key=...]关闭其决策键,然后运行bin/fm-afk-return.sh check。 船长裁决结果会列在“等待你处理”下,但不能豁免未关闭的阻塞项:每个阻塞项的来源信息会延期且不指定负责人,门禁会安全地保留所有未关闭的阻塞项。 记录归档后,在处理阻塞项的同时,按照生成的主测试框架监督协议恢复每次唤醒时的完整响应能力,避免门禁造成盲目等待。 门禁开启时可以回应 Bearings 请求,摘要会在 Charted Next 中以(return-catchup)警告行显示追赶状态,并指出仍有哪些事项在阻碍进展。 对舰队采取行动——派遣、指挥、合并或任何其他常规船长工作——仍须等到检查成功退出后才能进行。 检查成功后,按照常规拆除流程关闭简报在“已完成,待清理”下列出的每项任务(运行bin/fm-teardown.sh <task>,绝不强制执行;拒绝操作表示必须停止并调查),并以结果为导向的措辞告知船长这些工作进程已关闭。 - 带有当前操作前缀(
FM_OPERATIONAL_PREFIX,U+2063 INVISIBLE SEPARATOR 后接FIRSTMATE_OP:)的消息,或旧版裸FM_INJECT_MARK守护进程升级消息 -> 不要退出,继续处理。 - 与记录匹配的操作门铃消息(格式恰为
: Firstmate operational input waiting: read '<path>' ...)-> 运行bin/fm-operational-input.sh open '<path>';成功后,不要退出,继续处理其打印的升级内容。 如果失败,该门铃并非 Firstmate 发出,因此将其视为其他未标记消息。 切勿将仅仅看起来像 Firstmate 输入的 ASCII 文本(例如手动输入的FIRSTMATE_OP:标签)视为内部消息。 - 来自 Stop hook 或监督主机的
Stop hook feedback唤醒,或针对该分支的 Grok 后台任务完成通知 -> 不要退出,继续处理;这是自动监督,不是船长发来的消息。 - 已经离开时再次调用
/afk-> 保持离开状态(刷新);这不会触发退出。
遇到模糊情况时,优先判定为退出:船长已在场比节省令牌更重要,而且误判退出可以自我纠正(船长会重新运行 /afk)。
当船长想在保持在线并聊天的同时使用这种节省令牌的监督模式——普通消息不应让它退出——应使用 /quiet(kunchenguid/firstmate#2356),而不是 /afk。
与审批权限正交
afk 改变的是如何通知 captain,以及在由 captain 决策的节点上会发生什么,而不是由谁批准什么。
“Away”永远不意味着“批准得更多”或“批准得更少”。
准备好合并的 PR 仍遵循 AGENTS.md 第 7 节中的合并权限,而需要决策的发现仍遵循 ask-user-authority 策略;任何需要 captain 决定的事项仍须等待 captain 明确表态。
只要 away 姿态记录存在,任何在实时头提交上检查全绿的拉取请求都可以依据 away 权限合并;captain 的话具体指的是哪一个,由 away 会话自行解读,而那些话没有要求合并的内容则暂缓处理,等 captain 返回。
Away 权限永远不会解除 captain 的暂缓决定,并且会在 away 记录归档时失效。
--allow-red、--allow-missing 和 --allow-no-pipeline 仍仅限有人值守时使用,away 记录存在期间会被拒绝。
依据 away 权限进行的合并必须同步完成;away 记录存在期间,fm-pr-merge.sh 会拒绝自动合并,以及任何无法证明会立即合并的 GitHub 队列状态。
同样的门槛约束执行操作的任何一方:在 Pi 上,暂留的 main 的常设权限会转移到监督分支,该分支严格遵守这些规则;进入 away 状态时记录的支出上限也会在 away 记录存在期间由 fm-spawn.sh 对双方执行。
captain 的 away 说明是其离开前给出的明确指令,会逐字记录,并由 away 会话在事件使这些指令适用时根据当下判断执行;说明未提及的内容一概不涵盖,绝不会类推应用,并会在归档时失效。
无论说明如何,破坏性、不可逆和安全敏感操作都不能预先授权。
守护进程仍在运行的环境
在仍会启动守护进程的 harness 和 backend 组合中,以下机制保持不变。 Pi、pi-signed、运行监督主机的 home,以及上述 T3 Code 限制均不属于本节范围。
操作前缀约定
守护进程会将每条当前升级消息构造为由 bin/fm-operational-input.sh 负责的 away-supervisor 类型;其封套以 FM_OPERATIONAL_PREFIX 开头:FM_INJECT_MARK(U+2063 INVISIBLE SEPARATOR),后接稳定的 FIRSTMATE_OP: 标签。
在推出期间,旧版守护进程升级消息仍接受单独使用 FM_INJECT_MARK 的形式。
U+2063 没有常规键盘按键,并且作为 UTF-8 文本通过终端传输时会保留,但 Claude Code(已在 2.1.280 版本验证)会从每条提交的提示中移除它以及所有其他不可见字符,无论提示是键入、粘贴还是作为启动提示传入。
对于列为会剥除该标记的主 harness(Claude Code),守护进程会将封套作为记录写入此 home 的 state/operational-inbox,并且只键入指明该记录的纯文本提醒。
只有当 open 验证了此 home 中的记录时,该提醒才是 Firstmate 发出的,因此仅凭提醒的形式绝不算数;由于载体不会跟踪提醒是否已被使用,只要对应记录仍存在,逐字复制一条有效提醒并粘贴回来,也会被视为 Firstmate 发出的。
Firstmate 正是通过这种方式,将守护进程升级消息与同一窗格中的真实消息区分开来。
对于其他 harness,操作前缀会随消息文本一起传递;两种载体都不依赖 harness 层面的键入与注入检测。
忙碌保护与输入框保护
守护进程绝不会向正在使用的窗格注入内容。T3 Code 没有输入框,使用其原生状态和轮次传输机制,该机制由
docs/t3code-backend.md 所有。每次向窗格注入前都会运行两项检查,通过 bin/fm-backend.sh
分派给主管自身的后端(参见主管后端配置):
- 主窗格忙碌保护——
pane_is_busy在后端提供原生busy状态时信任该状态,否则只将渲染后的输出与检测到的主运行环境特征进行匹配。 这项范围严格的投递保护绝不会将已记录的工作任务判定为忙碌,也不会使用供应商特征的全局并集。 - 输入框状态保护——
inject_msg会读取fm_backend_composer_state给出的完整empty/pending/pending-unproven/unknown判定结果,只有明确判定为empty时才会注入。 其他所有判定结果(包括未来新增的结果)都会推迟处理,其中包括无法读取的窗格、几何位置不明确、空白且无法识别的行,以及 Agent 退出后遗留的裸 Shell 提示符。 每个适配器只向全舰队通用的屏幕分类器bin/fm-composer-lib.sh提供捕获和能力事实;所有形态与判定结果均由该分类器负责。 它会将已确认空闲的输入框保留为empty,但要求 Shell 符号必须由真实容器包围;运维约定参见docs/herdr-backend.md中的“输入框与注入安全”。 对于需要判断输入框是否处于不安全状态的调用方,pane_input_pending是经过测试的故障时关闭谓词:除精确的empty外,其他所有结果都会被视为待处理。
主窗格忙碌,或输入框的判定结果不是 empty,都会推迟注入;已缓冲的升级消息会保留在 state/.subsuper-escalations 中,并在下一次维护周期重试。
在 afk 模式下,输入框保护属于多一重保障(此时没有人在输入),但它可以防范队长返回后消息尚未送达的竞态窗口、失效的 Shell,以及守护进程先前注入但尚未发送的内容。
最大延迟逃生机制(守护进程绝不能无声卡住)。
如果任何内容缓冲超过 FM_MAX_DEFER_SECS(默认值为 300),守护进程会尝试执行一次常规刷新;该操作仍要求端点处于空闲状态,且对于窗格后端,输入框必须明确判定为 empty。
此警报是纵深防御机制,不能替代确保所有真正空闲且受支持的输入框都可注入这一要求。
如果无法确认提交成功,守护进程会触发醒目且带速率限制的卡死警报:
在守护进程日志中记录一条 ERROR,注明最近一次投递失败;创建持久化的
state/.subsuper-inject-wedged 标记(返回简报的健康状态行会显示该标记);适用时闪烁 tmux 状态栏;并触发可配置且与后端无关的主动警报。
警报通道的设置由 docs/wedge-alarm.md 负责;主动证据由 docs/verification/supervision.md 中的“卡死警报通道”负责。
因此,保护机制的误报会造成可见的停滞,而不会导致无限期无声地空操作。
提交模型
在 tmux 和 Herdr 上,摘要只会通过不提交的字面文本发送(tmux 上使用 send-keys -l,Herdr 上使用 pane send-text)输入一次。
按 Enter 提交后,所选后端的提交原语会验证是否已送达。
会重试 Enter(只重试 Enter,绝不重新输入),直到后端确认提交已生效。
传送失败时会记录失败阶段(初始发送或 Enter 传送;若未执行确认重试且文本可能已在某些后端上输入,例如无法发送 Enter 的 herdr,则属于此阶段;或者 Enter 确认阶段)、负载的字节数,以及传输层自身的错误输出。
对于 tmux,通常会通过共享分类器确认输入框已清空;如果可用的测试环境隐藏了输入框,那么从空闲基线到繁忙状态的变化也能确认此提交触发了回合启动。
没有该基线时,繁忙状态绝不会将 unknown 输入框状态转为已确认。
对于 herdr,基线为空闲时,提交会先检查原生 Agent 状态,以确认确实启动了新回合;如果原生状态仍为空闲,则使用共享分类器:输入框已清空表示确认送达;仍有待处理文本则重试 Enter,并且只有在用尽重试次数后才会进入共享的繁忙队列判定。
如果该后端会使用输入框确认,则带边框的空输入框或仅含幽灵文本的输入框会被识别为空,而不会被误判为 Enter 被吞掉。
fm-send.sh 仅在其输入平面上使用同一原语;如果该平面的 Enter 确认被吞掉,脚本会以非零状态退出;普通的本地文本引导会使用持久化收件箱,不会把门铃提交视为送达证明。
繁忙队列中的 Enter 例外(opencode)。 OpenCode 会在处理回合期间保留排队文本,因此受支持的主管适配器会将最终送达判定交给 bin/fm-composer-lib.sh 中的 fm_composer_queued_enter_verdict,而不是仅凭可见文本就认定 Enter 被吞掉。
守护进程仅在后端给出 empty 成功判定时才会清空其缓冲区;docs/tmux-backend.md、docs/herdr-backend.md、docs/rex-backend.md 和 docs/t3code-backend.md 负责后端专属的确认信号。
分类策略
守护进程会封装 fm-watch.sh,将观察器作为子进程运行;每次可处理的观察器关闭后,都会呈现所有持久化唤醒;再由 bash 对每条已呈现记录进行分类,并在路由完成后确认已呈现的代次。
它会自行处理大多数常规情况,不消耗 firstmate 回合。
按下述路由规则选中的事件会作为预读过的单行批量摘要,升级至 firstmate 的上下文。
摘要会限制字节数,确保所有传输方式都能承载;如果截断了事件,或因超出预算而省略事件,摘要会指出一个 state/.subsuper-digests/ 文件,其中逐字保存了所有已缓冲事件,因此在处理被截断的事件前,请先读取该文件。
与 captain 相关的动词集合、已声明等待的词汇、状态跨度分类器和呈现标记约定定义在共享的 bin/fm-classify-lib.sh 中;每个主管都将自身的路由和舰队扫描作为该策略的使用方。
只要 state/.afk 存在,守护进程就会接管观察器,因此观察器会恢复为单次运行,由守护进程进行分流——两者绝不会同时进行分流。
按以下方式对每次唤醒进行分类;先应用引导收件箱例外,再按状态进行路由:
stale,且其详情以unread firstmate instruction: stuck-busy或steering-inbox busy bookkeeping unwritable:开头 -> 在离开模式和安静模式下,将明确的收件箱升级请求缓冲起来以供监督,不消耗工作器状态,也不进入瞬时过期恢复。bin/fm-task-inbox-lib.sh负责忙碌预算和记账约定;tests/fm-daemon.test.sh覆盖此使用方边界。signal,且新分类的状态区间包含与 captain 相关的事件 -> 按来源顺序升级每个事件。 非终止性进度动词即使其描述中包含旧式自由文本标记(例如PR ready、checks green、ready in branch或merged),也仍然是非终止状态;只有包含此类标记的纯旧式行才会触发升级。 区间内不含其他与 captain 相关事件的信号 -> 自行处理。signal或stale,且最新状态声明正在等待(无论是paused:外部等待,还是经验证的captain-held移交),都应跟踪暂停状态,而不是判断为卡死,无论其窗格显示为空闲还是忙碌。 新分类区间内未报告的、与 captain 相关的事件仍会立即触发升级,同时当前声明会独立维持暂停节奏。 如果没有未报告的可处理事件,则自行处理此次唤醒;当前声明优先于增强后的潜在卡死原因,因此绝不会按FM_STALE_ESCALATE_SECS节奏触发升级。 如果外部等待在超过FM_PAUSE_RESURFACE_SECS(默认为四小时)后仍有声明,housekeeping 会发送一次重新检查,并重置暂停窗口;只要 posture 记录仍存在,就绝不会重新检查 captain-held 移交。 窗口时长根据团队成员自己的最新状态行计算,因此只有追加一条不再声明等待的状态,才会结束此路由并恢复卡死检测。check-> 始终升级。检查脚本只会在 firstmate 应当唤醒时输出内容。stale,且状态为终止状态、纯旧式的与 captain 相关行,或无法识别的状态前缀(例如parked:)-> 升级。 非终止性进度即使其描述中包含旧式自由文本标记,或其已查看状态标记已匹配,也仍然是瞬时状态,因此应记录标记并自行处理。 如果窗格在超过FM_STALE_ESCALATE_SECS(默认为 240s)后仍为空闲,housekeeping 会将其作为潜在卡死情况升级。 对于 T3 原生不确定性,请遵循过期警报保留约定。 这会将卡死检测延迟限制在阈值加一个周期内:只会延迟,不会遗漏。 以blocked-on-prompt:和对话框名称结尾的过期详情表示窗格显示了该对话框,因此工作器无法继续,直到有人作答。 此路径已发送的升级会保留该名称,无论名称来自状态日志还是 housekeeping。 已报告过的终止状态仍会清除持久标记,与没有对话框名称的过期状态相同。 已声明的等待会维持暂停节奏,且不会携带该名称。 状态正常的团队成员可以自主工作,不会在任务中途等待 firstmate。heartbeat-> 自行处理。 守护进程每隔FM_HEARTBEAT_SCAN_SECS(默认为 300s)运行一次自身的轻量级 bash 团队扫描,作为兜底,用于捕获每次唤醒的分类器仍未读取的与 captain 相关事件。- 未知的唤醒原因会触发安全升级。
该升级送达后,会确认其精确提炼行;在本次离开会话期间,相同身份不会再次触发升级。
新的离开会话不会包含任何确认记录,因此已处理的身份可以再次出现一次。
尚未送达的身份仍会触发升级。
状态读取不确定性遵循下方 Dedupe 中引用的共享约定:不推进位置时仅报告一次。
升级事件最多会缓冲
FM_ESCALATE_BATCH_SECS(默认 90s;0 = 立即),然后合并为一条单行摘要,其中包含预读状态摘要和建议操作。 单行格式可确保在不同 harness 中提交时毫无歧义;上述载体则用于将其与普通 captain 消息区分开来。
注入加固
- 单行摘要 - 注入前会将嵌入的换行符折叠为字面 分隔符,因此无论使用哪种 harness,提交都毫无歧义。
- 监督端点上的忙碌状态和输入框防护 - 注入前,守护进程会运行后端的忙碌状态防护,并直接读取
fm_backend_composer_state。 对于窗格后端,只有empty才允许注入;pending会保护尚未输入完毕或被吞掉的输入,而unknown会保护无法读取的窗格和裸露的死 shell 提示符。 对于 T3 Code,请遵循原生注入契约。 其他所有窗格判定都会保留缓冲区以供重试,因此守护进程绝不会将摘要合并到 captain 尚未输入完的行中,也不会将其输入 shell。 - 窗格后端会将其捕获内容,以及声明式的样式、光标、身份和行能力传递给共享屏幕分类器;所有结构识别和判定逻辑仍位于
bin/fm-composer-lib.sh中。 带样式的捕获内容可让该模块在进行形状检测时,从经 ANSI 去除的屏幕中移除变暗/浅色以及深色 TRUECOLOR 的幽灵文本或占位文本,因此不会因移除幽灵内容而丢失深色边框。 因此,只有幽灵内容的输入框或空闲的带边框输入框(例如 claude 的│ > ... │)会被判定为空,但不会因此将无边框的 shell 提示符也判定为空。FM_COMPOSER_IDLE_RE可覆盖共享的空闲占位符正则表达式,但仅匹配并不足以绕过分类器针对特定形状的位置和 ANSI 弱化安全检查。FM_BUSY_REGEX可覆盖渲染后的交付防护,以及 Grok 的孤立任务状态回退机制。 空白或其他无法识别的输入行不提供任何肯定的容器证据,因此会推迟注入,以免将模态对话框或正在重绘的窗格作为注入目标。 - 最大延迟逃逸机制 - 请遵循上述有界刷新和警报契约以及警报设置指南。
- 已验证的提交模型 - 请遵循提交模型及其链接的后端确认契约。
- 标记剥离 -
strip_injection_marker会在分类或中继前移除当前操作前缀或旧版裸标记,因此 firstmate 看到的摘要文本是干净的;open输出的记录支持型门铃摘要也已剥离标记。 - 可移植的单例锁 - 守护进程使用仓库的可移植锁辅助程序
(
fm-wake-lib.sh),而非 macOS 上不存在的flock。 - 在 signal/stale/scan 之间去重 - 这三条路径都使用
bin/fm-classify-lib.sh中定义的共享状态呈现标记,因此同一摘要中已成功分类的内容范围不会被其他路径再次升级。 绝不要将报告为不可读的状态视为已分类;此标记契约由共享库头部定义,而且对于非终态进度行,该标记不会清除或抑制可能卡顿状态的计时。 - 自动发现监督窗格 - 监督后端配置负责后端和目标解析,以及对不支持的传输方式进行拒绝。 启动时会记录这两种解析来源,以便发现虽能解析但错误的回退配置。
陈旧产物生命周期
将 state/.subsuper-escalations、其 .since 伴随文件、state/.subsuper-inject-wedged 和 state/.subsuper-unknown-acked 视为会话范围的交付产物,而不是持久化的工作记录。
始终通过 bin/fm-afk-launch.sh 进入;仅在全新进入时清除上一会话的产物,刷新时则保留当前会话的缓冲区。
始终通过 bin/fm-afk-launch.sh stop 退出;在守护进程关闭并刷新期间保留 state/.afk,随后将其清除,并最后归档状态记录。
docs/herdr-backend.md 中的 “Away-mode supervisor support” 和 docs/t3code-backend.md 中的 “Away mode” 负责后端机制,docs/verification/runtime-backends.md 中的 “Away-mode transport” 负责当前有效证据。
可靠性属性
必须满足以下属性:
- 队列发布后不会丢失任何内容。 守护进程会将每个已呈现的唤醒事件持久保留,直到路由完成且处理后确认成功,因此中断后会将相同的工作重放给守护进程或其后继进程。
- 卡顿检测必须有界延迟,且不能丢失数据。
- 对已声明的外部等待,会按照单独、有界且考虑条件的节奏重新检查,而不会将其错误标记为卡顿;只要状态记录存在,为队长保留的项目就不会被重新检查。
- 全面扫描作为关键字分类器的后备。
- 守护进程会保留单实例可移植锁、崩溃循环退避、面板消失防护,以及由信号捕获的关闭流程;退出前会刷新已缓冲的升级事件。
FM_INJECT_SKIP(默认值为 heartbeat)会强制自行处理匹配的类型,覆盖分类结果。
请谨慎使用。