Documentation
¶
Overview ¶
本文件枚举本机可广告的单播地址,供 init 配对片段的 addr 使用。
职责:
- 从网卡地址里筛出可给对端抄的 IPv4(排除 loopback / link-local)
- 把 listen 上的通配地址换成排序后的第一条,拼进配对 addr
边界:
- **不写 listen、不改配置**:探到的 IP 只出现在配对片段。绑到某一张 网卡会让 127.0.0.1 连不上,DHCP / Tailscale 一变 agentd 也起不来。
- 本期只要 IPv4;IPv6 留给后续(本仓库远程场景是 Tailscale CGNAT)
本文件实现 handoff agentd 子命令:加载配置、初始化统一日志、打开 SQLite 存储、 构建 HTTP/WS 服务并监听。agentd 是本机/配对主机上的长驻服务,是任务的执行入口。
职责:
- 按序完成 bootstrap:config.Load → logx.Setup + slog.SetDefault → pathenv.Apply(PATH 补全,先于一切 fork 子进程)→ store.Open → agentd.NewServer
- 对外服务前做启动恢复(RecoverOnStartup):探活未终结任务的执行器,重建订阅或转 failed
- 启动任务卡住看门狗 goroutine(RunWatchdog),长时间无事件产出触发 stalled 唤醒协调者
- 监听配置中的 Listen 地址,进程生命周期与 HTTP server 一致
- 经 agentd.Shutdown 提供优雅关停:SIGINT/SIGTERM 停收新连接 → 等在途请求 → 停看门狗 → 关库 → 放锁;正常关停 exit 0,供进程管理器据此拉起新版
边界:
- 不创建任务/工单:任务生命周期由 manager 驱动(executor 按 --executor 挂载)
- 不决定何时停机:信号与进程内触发都汇到 agentd.Shutdown,本文件只接线
本文件实现 handoff attach 子命令:在终端跟随任务实况。
职责:
- 从 agentd 的 render 流式接口取任务实况,原样打印到 stdout,Ctrl+C 退出
边界:
- 不解析实况内容:render.log 是模型回合文本原样增量,这里只做搬运
- 不连 executor、不碰任务目录:一切经 agentd 的 HTTP 接口
为什么不再 exec 外部命令:旧实现用 syscall.Exec 换进程进 tmux(本机), 或 ssh -t <host> tmux attach(远程)。tmux 拆除后实况改由 agentd 落盘 + 流式吐出,attach 退化成一个普通 HTTP 客户端——顺带拿到三个收益: 远程不再需要 ssh(复用 agentd 连接与鉴权,配置里的 user 字段对 attach 不再必要)、 Windows 协调者可用(syscall.Exec 在 Windows 上直接返回 EWINDOWS)、 断线可凭已收字节数续传。
handoff card 命令族:任务卡账本的命令面。谁是机器谁是人分得清: stdout 只出机器 JSON(一行一对象;list 缺省表格是唯一例外,--json 切换),人话走 stderr。状态名用中文原文(与 workflow 定义一致), 不设英文别名。
card dispatch:按模板拼装 prompt,带上纪律块**角色名**(正文由 agentd 注入), 走既有 dispatch 通道; 派发即认领(CAS 进「进行中」就是 claim,第二个会话干净失败); task 回链 + 模板版本/纪律角色名快照落事件。
card min-b:切换期一次性命令——把 B 号水位垫到历史总账 max B, 此后新建卡号严格大于历史号,markdown 旧账与账本新账永不撞号。 Hidden:日常工作流用不到它,藏起来防误用。
card dispatch --step 的 CLI 装配层:构造 ledgerstep.StepRunner 并把结果编码成 JSON。 编排本身在 internal/ledgerstep——看板按钮(经 /api/cards/{id}/step)装配的是同一个 StepRunner,只是注入不同的传输,单一编排真相源由此落实。
回合末四分法的两个写入口:card accept(完成项的验收结果)与 card needs(阻断需人工的等人标记)。
职责:把 ledger.Store 上已有的 RecordAcceptance / MarkNeedsHuman / ClearNeedsHuman 三个方法接出 CLI 门面。 边界:只落事件,不改卡状态——状态流转一律走 card move(由工作流 gate 校验);验收判据文本归 card update --accept,本文件只管「验的结果」。
card wait:账本单流多路 wait。
职责:跟一张卡(或其动态重算的子树)的账本事件流,逐事件输出,全部成员 达骨架终态即退出。 边界:不碰执行域的 task wait(那是 cmd/wait.go 的 handoff wait <task>); 两者是分层关系——外层用本命令管卡的调度,醒来后处置具体 task 事件仍用 执行域动词(reply/approve/continue)。
本文件实现 handoff console 子命令:用主令牌换一张一次性 ticket, 并把兑换 URL 交给系统浏览器(或打印出来给桌面壳用)。
职责:
- 调 client.IssueAuthTicket 取兑换 URL
- 默认调系统浏览器打开;--print-url 只打印(这是桌面壳的接线点)
- 设备名缺省取本机主机名(CLI 没有 User-Agent 可推断)
边界:
- 不实现任何鉴权逻辑:凭据的签发与校验全在 agentd 侧
- 不管前端是否存在:本命令的成功判据是「拿到兑换 URL」,兑换后落地页上有什么 由 agentd 决定(W5a 之后是真实控制台,不带 embedweb 标签构建时是 stub 说明页)。 不要因为落地页的形态变化而改这里的成功判据
- --target 可用,但那是**诊断入口**不是产品路径(产品路径是「只连本机 agentd,由它向远端转发」),不要因为它好用就当成跨机方案
本文件实现 handoff continue 子命令:向任务续发修改指令。
职责:
- 把协调者的修改指令经 client.Continue 原样透传给 executor(同一会话续接, 上下文完整保留;任务必须处于 waiting_review)
- 成功时单行输出 {"ok":true}(供上层脚本解析)
边界:
- 不解释指令语义,原文透传;任务状态校验由 agentd 判定并返回错误
handoff decision 命令族:裁决项。主会话回合末 open、用户 answer、 会话唤醒后 list 读答复——「推不推等你一句话」的闭环三件套。
本文件实现 handoff diff 子命令:取任务分支相对基准分支的审阅素材(git diff + 提交列表)。
职责:
- 调 client.Diff 拉取 diff 文本并原文输出到 stdout(协调者阅读/管道分析用)
边界:
- 不做 diff 语义判断;基准可经 --base 指定,缺省优先用任务基线提交,没有才由 agentd 按仓库默认分支推导
本文件实现 handoff dispatch 子命令:把本地 plan 文件(或 --prompt 直接指令) 派发到 agentd 执行。
职责:
- 读取本地 plan 文件并 base64 编码,连同项目身份/计划名/target/执行者/模型/ 分支/worktree 等参数一并 POST 给 agentd(body {project_id, plan_b64, prompt, ...})
- 派发的项目由 cwd 识别:读当前目录 git 仓库的 origin 离线算出 project_id, cwd 不是目标项目时用 --project <名字> 显式指定
- 远程派发时采集本地 HEAD 作基线随请求上送,并校验本地工作区完整性 (已跟踪改动拒发、未跟踪警告;--no-sync-check 关掉整块,--allow-dirty 只关拒发)
- 派发成功后在 stderr 打一行基线摘要(起点短号 + 任务仓库领先的提交数)
- 派发成功后在 stderr 提示执行机仓库的未提交改动(managed 工作树不含它们)
- 成功时单行输出任务 JSON(state=running,供上层脚本解析任务 id)
边界:
- 只做文件读取与上传,不校验计划内容语义(解析与执行由 executor 负责)
- --no-terminal 在本文件只注册 flag 并参与「是否弹终端」的判定骨架; 弹终端默认**不弹**(cfg.Terminal.Auto 默认 false),配置 auto: true 时 才在 darwin 弹窗,--no-terminal 用于逐次关闭
本文件实现 dispatch 的本地工作区完整性校验(backlog B29)。
职责:
- 把 git status --porcelain 的输出分成「已跟踪改动」与「未跟踪文件」两类
- 已跟踪改动拒发(--allow-dirty 可放行),未跟踪只警告
- 全部提示走调用方给的 stderr writer
边界:
- 只看当前工作目录(cwd)这一棵树;agentd 侧任务仓库的脏检查是另一回事, 由 internal/agentd 的 ensureCleanWorktree 负责,两者互不替代
- 不发起任何网络请求:拒发必须发生在 HTTP 请求之前
- 不解释 git 的退出码:status 本身失败时降级放行,不把派发挡死
本文件实现 handoff done 子命令:归档任务。
职责:
- 审核通过后调用 client.Done 把任务置为 completed 并回收 executor(任务必须 处于 waiting_review)
- 成功时单行输出 {"ok":true}(供上层脚本解析)
- 携带可选完成说明(--note)并在 stderr 提示保存结果
边界:
- 不做 push 等归档后动作(按任务配置决定是否 push 不在 MVP 范围)
- 不做说明内容的校验与加工(只校验长度)
本文件定义 CLI 的退出码语义:把「失败的类别」编码进进程退出码。
职责:
- 提供 exitCodeError 包装,让特定失败带上专属退出码
- 提供 ExitCode,供 main 把 Execute 返回的错误换算成退出码
边界:
- 不打印任何东西(错误文本由 cobra 打到 stderr)
- 不决定「什么算失败」,只决定「这次失败对外表达成几号」
本文件实现 handoff fetch 子命令:读取任务仓库内文件内容(审核取上下文用)。
职责:
- 调 client.Fetch 拉取仓库内相对路径文件并原文输出到 stdout
边界:
- 不修改文件;路径由协调者指定,逃逸路径由 agentd 拒绝
footprint.go —— `handoff footprint` 命令:体检全部任务的进程足迹。
职责:
- 拉取对端全部任务(含已归档)的进程占用与判定结论并渲染
边界:
- **只数不杀**:本命令不回收任何进程。清扫由 agentd 在 executor 判死时 自动完成(见 spec §3.4),本命令只负责让人看见
- 不改任何任务状态、不发事件
本文件实现 handoff frames 子命令:读任务的结构化回合帧。
职责:
- 调 GET /api/tasks/{id}/frames,把 ndjson 流每行原样打到 stdout
边界:
- **不做人类友好格式化**:本命令是 handoff tui(W4e)与脚本的数据源, 人要看好看的有 Web 控制台。与 handoff tasks 的「一行一个 JSON」同风格
- 不解析帧语义:只做行搬运与心跳过滤
- 任务 id 是完整 UUID 精确匹配,没有前缀补全(与全部子命令一致)
本文件实现 handoff graph 子命令族:对仓库内代码图数据的本地只读查询。
职责:
- graph validate: 引用完整性 + 可选 --stale 保鲜检查,供 CI 与扫描后自检
- graph views: 列出可用视图(diffs 目录)
- graph chain: 焦点(可多个,并集)的下游调用链
- graph who-calls: 焦点(可多个,并集)的上游调用方——影响面查询
边界:
- 只读 --repo 指向的本地文件,不发任何网络请求、不依赖 agentd 存活 ——spec 2026-08-19-codegraph-design §2/§6 的硬约束,agent 离线可用
- 不产出/修改图数据(扫描配方见 docs/codegraph-scan-recipe.md)
本文件实现 handoff init 子命令:一台新机器的问答式配置。
职责:
- 探测四家 executor 的状态并成表打印
- 问答编排在 internal/initflow(AskAll / MaybeInstallService)——本文件 只负责把它问到的答案写进 config.yaml
- 末尾打印本机 token 与现成的配对 yaml 片段
边界:
- **不发起任何真实模型调用**:探测一律用轻量本地判据(见 internal/toolchain)
- **不主动装服务,但会问**:角色含执行机且 stdin 是终端时,init 会追问一句 是否托管,答 y 则调 initflow.InstallService(与 handoff service install 同一条路径)。托管是「重启后 agentd 还回得来」的唯一保障,只留一行提示的 触达率不够(B71)。Linux 上非 root 时一律不代跑,只打印 sudo 命令
- **不阻断任何选择**:探测结果只影响默认值与标注;没装任何 executor 也能配完 (纯协调者机的正常情况),选了「未登录」的执行者只警告不拦
- stdin 非 tty 时一问不问:init 会被 install.sh 经管道调起,问了没人答, 卡住比不问糟得多
本文件是 init 在真终端上的 huh 问答实现。
职责:
- 用 huh 的 Select / Input / Confirm 实现 initflow.Prompter
- 把用户取消(Ctrl-C、huh.ErrUserAborted、context 取消)译成 initflow.ErrCanceled
边界:
- **只服务 TTY**:测试不得走这里。CI 没有真终端,huh 会挂死; 测试经 newInteractivePrompter 缝换成脚本化实现
- **取消 / 失败绝不写配置**:本文件只返回错误。写盘是 RunE 的事, 见到错就不 Save,避免留下一份只配了一半的 config.yaml
- 不负责问题集合;问什么仍由 initflow.AskAll 决定
账本命令族的公共底座。职责:解析账本库位置并打开、CLI 侧 actor 标识、 破坏性动作确认。边界:本文件不含任何具体动词逻辑。
为什么 CLI 直连账本库而不经本机 agentd HTTP(有意偏离既有惯例): 账本凭据本来就在协调机 config 里,CLI 与 agentd 是对等消费者;账本 操作不应依赖本机 agentd 存活;执行域必须走 HTTP 是因为 task 数据在 远端机器的 SQLite——账本(中心库/本机回退)没有这个约束。
本文件实现 handoff machines 子命令:列出本机视角的全部机器与探活结果。
职责:
- 调 GET /api/machines,把「本机 + 配置里的 targets」投影成表格或 JSON
边界:
- 只读投影:不做机器配置写操作(增删改 targets 走改 config.yaml)
- 不可达的机器必须带原因——一句干巴巴的「不可达」等于让人去猜
本文件实现 handoff permission-mcp 隐藏子命令:Claude Code 的权限裁决 MCP server。
职责:
- 以 stdio JSON-RPC 提供一个 ask 工具,claude 经 --permission-prompt-tool 调用它
- 把每次授权请求经 unix socket 转给 agentd 侧的 adapter,阻塞等待人工/审批者裁决
- 把裁决还原成 claude 认识的 {"behavior":"allow"|"deny"} 返回
边界:
- 不读 handoff 配置、不连 agentd HTTP:唯一对外面就是 --sock 指定的路径, 被监管的 executor 因此拿不到 agentd token
- 不做任何审批判断:连不上就一直重试等待,绝不自作主张放行(fail-closed)
为什么是隐藏子命令而不是独立二进制:claude 侧只需要一个可执行文件路径, 复用 handoff 自身避免了额外分发与版本漂移。
日志例外(本文件唯一允许不用 slog 的地方):stdout 是 JSON-RPC 通道,任何 非协议内容混入都会让 claude 侧解析失败,且本进程是被 claude 拉起的短命子进程, 不接 agentd 的 logger——诊断只能走 stderr 的 fmt.Fprintf。
本文件实现 handoff project 子命令族:把一个项目登记到本机与(可选的)一台 远程开发机上,并维护「项目 × 机器」的位置表。
职责:
- project add:把 cwd 登记为本机位置;--target 时一并登记到那台机器
- project ls:列出位置,并显示每条的实际状态(登记与磁盘漂移时看得见)
- project rm:注销位置
- project edit:改一条位置的引用名与/或路径(不动 project_id)
边界:
- 不自己 ssh、不自己 clone:clone 由目标机上的 agentd 执行,用它自己的 git 凭据
- 不删磁盘上的仓库:rm 只删登记
- 不决定「项目在那台机器的哪个目录」:远程落点由那台机器的 repo_root 决定, 本机一个远程细节都不需要知道(spec §6.2)
ptyhost.go —— 隐藏子命令 handoff _ptyhost:单个 PTY 会话的承载进程。
职责:解析 --spec,把控制权交给 hostproc.Run(阻塞到会话收摊)。
边界:不做任何业务判断:全部逻辑在 hostproc.Run 里,本文件只是 cobra 包装;它不面向 用户,Hidden=true,由 agentd 自己拉起。
本文件实现 handoff pull 子命令:把远程执行机上的任务分支同步到本地仓库。
职责:
- 查任务拿到 target/仓库路径/分支,取回任务分支的提交并 fetch 到本地同名分支
- 决定走哪条路:优先经 agentd 的 HTTP 面取 git bundle,仅在对端过旧(404)时 退回 ssh 老路
边界:
- 只 fetch,不 checkout、不合并(合并是协调者的决定)
- 本机任务(无 target)无需同步:代码本来就在同一台机器上
- 不做 git bundle 的生成:那在 agentd 侧(internal/agentd/bundle.go)
reclaim.go —— handoff reclaim 子命令:回收终态任务残留的 managed worktree。
职责:
- 无参:列出仍占着 managed worktree 的终态任务(净/脏/元数据残留/判不出)
- 带任务 id:回收那一个;脏树默认拒绝并报出改动清单,--force 才强删
边界:
- 不删任务分支(协调者的工作成果),每次成功输出都明说这一点
- 不删任务目录(失败任务的排查素材还在里面)
- 不改任务状态:回收前后 handoff show 看到的状态一致
本文件实现 handoff reply 子命令:回答一个待办工单(权限门/提问)。
职责:
- 把协调者的裁决转成应答原文:--approve → "allow"、--deny [--reason] → "deny[:原因]"、 --answer 原样透传,经 client.Reply 交给 agentd
- 成功时单行输出 {"ok":true}(供上层脚本解析)
边界:
- 不解释应答语义:answer 原样落库,含义由 manager 侧解释(allow → once / 其余 → reject)
- 不校验任务状态(工单是否存在、已回答等由 agentd 判定并返回错误)
本文件实现 handoff resume 子命令:解开卡死的任务。
职责:
- 调用 client.Resume 让 agentd 重投「已落库但未送达 executor」的应答, 并对断连窗口内丢失的回合终态做会话对账(B38)
- 原样输出恢复报告 JSON(重投条数 / 对账结果 / executor 是否已不在 / 收尾状态 / 结论)
边界:
- 不自己判断任务是否卡死,也不改任何状态:判定与收尾全在 agentd 侧 (Manager.RecoverStuck),CLI 只负责发起与呈现
- 与 continue/done 的分工:那两条要求任务已在待审核;本条专治两类中间态 ——「reply 拿到 502 之后 reply/continue/done 三条路全封死」,以及 「agentd 与 executor 断连期间回合已完结、终态事件丢失、任务冻死在 running」
Package cmd 提供 handoff 的 cobra 命令行入口。
职责:
- 定义根命令与全局 flag(--agentd / --target / --config)
- 提供 TargetEndpoint 辅助函数,供各子命令换算实际 agentd 端点
边界:
- 不包含具体业务逻辑(dispatch/gate 等子命令由后续任务补充)
- 不在此处初始化日志,由各子命令按需调用 logx.Setup
本文件实现 handoff run 子命令:在任务仓库远程执行审阅命令(跑测试/lint)。
职责:
- 按参数个数分档拼接命令行(单参数=shell 原文透传,多参数=逐个转义), 交给 agentd 执行(sh -c,10min 超时),合并输出原文打印; 非零退出码以错误返回(cobra 打印到 stderr),输出已先行打印
边界:
- 只透传命令,不解释输出语义;命令执行于任务仓库,由 agentd 限时回收
本文件实现 handoff service 子命令:把本机 agentd 交给进程管理器托管。
职责:
- install:解析当前二进制与配置路径,生成并安装服务单元,复核起来了
- uninstall:停止并移除单元
- status:报告托管状态
- start / stop / restart:改已装单元的运行状态,不改单元定义本身
- stop 额外收口本机 PTY 会话——它是**唯一**能表达「显式停止」意图的地方
边界:
- 不替代进程管理器:start/stop/restart 是对管理器下指令,不自己 fork 或 kill agentd;单元没装时一律硬拒,不代为 install
- 不改 handoff 的配置文件:托管与配置是两件事,配置走 handoff init
- restart 不碰 PTY 会话:重启的意图是「让改动生效」,不是「结束我的终端」。 agentd 那一侧分不出这次 SIGTERM 是 stop 还是 restart,所以区分只能在这里做
- 托管之后 agentd 的形态会变:手动 Ctrl-C 会被管理器拉回,停服务要用 handoff service stop;彻底摘掉托管用 handoff service uninstall。install 成功时会把这两种处置打给用户
本文件实现 handoff sessions 子命令族:列出与吊销浏览器会话。
职责:
- sessions:列出全部会话(含已吊销,显式标注)
- sessions revoke <id>:吊销指定会话
- 渲染前净化设备名:它来自客户端,可能含 ANSI 转义序列
边界:
- 不吊销主令牌:主令牌不可吊销(换它等于全部重配),本命令只管会话
- 不做交互确认:吊销一个会话是可恢复的(重新 handoff console 即可), 不值得一道确认门
本文件实现隐藏子命令 handoff _shim:执行者进程的承载壳。
职责:
- 解析 --spec,把控制权交给 prochost.RunShim(阻塞到执行者退出)
边界:
- 不做任何业务判断:全部逻辑在 prochost.RunShim 里,本文件只是 cobra 包装
- 不面向用户:Hidden=true,不出现在 help 里。它由 agentd 自己拉起, 人手动跑没有意义(缺 spec.json 就什么都做不了)
本文件实现 handoff show 子命令:输出任务的完整现场快照。
职责:
- 调用 client.Attach 拉取任务 + 待办工单 + 最近事件,单行输出完整 AttachInfo JSON—— pending_tickets 是协调者恢复现场(「我还没答哪些」)的关键数据源
边界:
- 只读快照,不修改任何状态
- 二期起快照命令从一期 attach 更名而来:attach 改为终端实况(见 attach.go), 本命令是协调者会话恢复的关键数据源,供 wait/tasks/show 之外的脚本解析
本文件实现 handoff skill:报告与同步内嵌 skill 的安装状态。
职责:
- handoff skill:逐落点报告是否与当前二进制一致
- handoff skill install:把内嵌内容装到本机各家 agent
边界:
- 不含安装逻辑本身(在 internal/skill):本层只做参数、打印与退出码
- 不装到远端:skill 服务于协调者,协调者在本机
本文件实现 handoff status 子命令:一条命令回答「这个 agentd 能不能用、是什么」。
职责:
- 调 client.Status 取服务端聚合结果,渲染人读文本(默认)或 JSON(--json)
- 把老 agentd 的 404 直译成一条**成功的**诊断结论
- 退出码只回答「能不能用」:0=可达且鉴权通过,1=够不着
边界:
- 不做探活:判据在各 adapter 里,服务端已经做完,本层只渲染
- 不因两边版本不一致而阻断:handoff 没有兼容矩阵,revision 不同不等于 不兼容,并列报出交给人判
本文件实现 handoff stop 子命令:主动中止一个还在跑的任务。
职责:
- 调用 agentd 的 stop 路由,停 executor、作废挂起工单、任务落 failed
- 依据响应体 worktree_removed 打印与实际行为一致的提示:managed worktree (agentd 建的)已删则如实告知,用户自带 worktree / 原地模式则说明保留
边界:
- 不删任务分支(那是协调者的工作成果,审阅/回滚仍可切回分支)
- 不做「停完再重派」:重派是独立决定,由协调者显式 dispatch
本文件实现 handoff tasks 子命令:列出全部任务。
职责:
- 调 client.ListTasks 拉取任务列表,每行输出一个任务 JSON(供上层脚本逐行解析)
- --all 时走跨机汇总(GET /api/tasks?scope=all),仍是每行一个任务 JSON, 机器应答情况走 stderr——stdout 是给脚本按行解析的任务 JSON 流,往里掺人话 会破坏既有消费方
边界:
- 只做列表展示,不做任何状态判断与筛选
handoff template 命令族:派发配方的查询与不可变版本写入。
本文件实现 handoff upgrade:一条命令巡检并升级本机与全部 target。
职责:
- 不带参数(或 --check):巡检表——列出所有机器的版本与结论
- --now:升级所有落后的机器(远端全部处理完,本机最后);--target 只升那一台
- --force:越过闸一(活跃任务)。**永不越过闸二(非托管)**
- --rollback:本机回滚(不接 --target,回滚是单机应急动作)
数据流(spec §4.2):本机下载各机平台的资产并校验 → POST /api/update 把 tar.gz 原文推给远端(执行机无需出网)→ agentd 复检两道闸、再校验、解包、 自检、原子换版 → 触发优雅关停由进程管理器拉起新版 → 本 CLI 轮询 status 确认新版本上线。
边界:
- 会通过接口触发 agentd 重启(本机最后:它会重启操作者正用着的 agentd)
- 部分失败不中断其余:机器之间没有事务关系,逐行报告,任一台失败退出码非零
- 处置建议必须对症:非托管不给 --force(它不越过闸二),够不着只报原文不编处置
本文件实现 handoff version 子命令:打印本二进制的版本标识。
职责:
- 首行输出纯版本字符串,供机器精确比对
- 其后输出 revision / Go 版本 / 平台三行,供人排障
边界:
- 不联网、不读配置文件:这条命令只回答「我是谁」。它必须能在一台刚装完、 还没有 ~/.handoff/config.yaml 的机器上跑通
- **首行格式是对外契约**:B54.3 的自更新自检会拉起新下载的二进制跑本命令, 把首行与期望 tag 精确比对(见 spec §4.6 步骤 ⑤)。改这一行的格式等于改 协议,必须同步改自检侧
本文件实现 handoff wait 子命令:阻塞等待任务的下一个可动作事件并输出单行 JSON。
职责:
- 调用 client.WaitEvent(progress 不唤醒、断线自动退避重连、cursor 续拉), 事件到达时把完整事件 JSON 单行输出到 stdout(供上层脚本解析)
- --notify:事件到达时发 macOS 系统通知(spec §7 风险#4 的兜底:协调者会话 不在时提醒其重新拉起),失败仅 Warn 不影响主流程
- 收到 SIGINT(Ctrl+C)时由进程默认行为终止,WaitEvent 随 ctx 取消退出
- 任务结束事件到达时自动同步远程任务分支到本地(输出走 stderr,不污染 stdout 的事件 JSON 契约)
- --follow:持续订阅同一任务的事件流,每条事件单行输出,直到任务终结 (failed 事件或被 done 归档)。此模式下 --timeout 的语义是**空闲**上限 ——距上一次收到任何帧(含被过滤掉的 progress)的时长,且跨重连累计
- --follow 每次建连前先对账:本机 cursor 之后有积压时吐**一行** backlog_summary (带 missed/stale/actionable),把 cursor 推到当前水位,积压事件不再逐条重放 ——stdout 每行是一次会话唤醒,逐条重放会把一次重连变成 N 次唤醒
- --until-done:B67 依赖门闩。--timeout 在此时是**总时限**,progress 等任何 中间帧都不能续命;成功只输出一行 archived,等待/失败/超时 stdout 全空
边界:
- 不做事件语义判断与审批(审批在协调者脑中),事件原样输出
- 不覆盖「协调者会话被关闭」:Monitor 是会话级的,会话没了订阅就没了, 本命令给不出任何补救(spec §7.2 明确接受的边界)
- --until-done 不读写审核者 cursor、不自动派发/回答后续任务:它只是 「真实 archived 到达」的唤醒门闩,不是审核流程的替代品
handoff workflow 命令族:状态机形状聚合的命令面。不可变版本化—— put 永远产生新版本;migrate 是三处破坏确认之一(批量改卡的呈现)。
Index ¶
Constants ¶
const ( ExitFailure = 1 ExitTimeout = 124 )
退出码约定。
为什么不全用 1:wait 的无人值守场景(cron/脚本挂在后台等唤醒)拿不到 stderr, 只能看退出码。全是 1 的话,「等满了时限」与「token 没同步导致鉴权失败」这两件 处置完全不同的事,脚本无从区分——前者该继续等,后者该立刻报警。 124 沿用 timeout(1) 的惯例,也与 handoff run 里被杀命令的退出码一致。
Variables ¶
This section is empty.
Functions ¶
func ExecuteContext ¶
ExecuteContext 是带 ctx 的 Execute(同样先清理单次执行的残留状态)。
参数:
- ctx: 传给命令 RunE 的上下文(取消即中断长驻命令,如 wait)
func ExitCode ¶
ExitCode 把 Execute 返回的错误换算成进程退出码。
参数:
- err: Execute 的返回值(nil 表示成功)
返回:
- 0(成功)、错误自带的专属退出码,或通用失败码 ExitFailure
func LocalEndpoint ¶
LocalEndpoint 返回**本机** agentd 的地址与令牌,忽略 --target。
返回:
- addr: 本机 agentd 完整地址(含 http:// 前缀)
- token: 本机令牌
- err: 配置加载失败或本机 token 为空时返回
为什么需要它而不是复用 TargetEndpoint:登记是**两跳**(本机 + 目标机, spec §6.1),而 TargetEndpoint 读的是包级 targetName,指定了 --target 时 拿不到本机端点。两跳都要发,就必须有一个不受 --target 影响的取端点入口。
func TargetEndpoint ¶
TargetEndpoint 根据 --target / --agentd / --config 换算实际请求的 agentd 端点与令牌。
参数(读取全局 flag):
- --target 为空(本机模式):token 一律取本地配置 cfg.Token(服务端无条件要求 Bearer,无 token 的本机调用必然 401);地址由 localDialAddr 决议(loopback 照拨,通配/单网卡改拨 127.0.0.1,B85);显式 --agentd 优先;cfg.Token 为空时 返回错误
- --target 非空:从配置 Targets 中查出 addr/token(远程配对)
返回:
- addr: agentd 完整地址(含 http:// 前缀)
- token: 访问令牌
- err: 配置加载失败、target 未定义或本机 token 为空时返回
Types ¶
Source Files
¶
- advertise.go
- agentd.go
- attach.go
- card.go
- card_dispatch.go
- card_minb.go
- card_node.go
- card_records.go
- card_wait.go
- console.go
- continue.go
- decision.go
- diff.go
- dispatch.go
- dispatch_dirty.go
- done.go
- exit.go
- fetch.go
- footprint.go
- frames.go
- graph.go
- init.go
- init_huh.go
- ledgercli.go
- machines.go
- permission_mcp.go
- project.go
- ptyhost.go
- pull.go
- reclaim.go
- reply.go
- resume.go
- root.go
- run.go
- service.go
- sessions.go
- shim.go
- show.go
- skill.go
- status.go
- stop.go
- tasks.go
- template.go
- upgrade.go
- version.go
- wait.go
- workflow.go