feat(trigger): HTTP API 支持 options.steer(codex-app 原生 turn/steer) - #1445
Conversation
POST /api/trigger 新增 options.steer 布尔位,把飞书 plain-human 入口已有的 codex-app turn/steer 能力开放给 headless HTTP 调用方(riff core-only): - trigger-session:live worker 的三条下发路径(wait/async/plain)与三条 dormant fork 路径透传 codexAppSteerable;fresh turn 标记 opening root (canSteer 要求 root 与 head 同时授权);响应在实际受理时回显 steer:true, 幂等复用不回显。无法注入时 runner 静默降级为普通排队 follow-up。 - worker-pool:steer 组 superseded final 的 daemon 守卫从仅 lark sink 扩到 http_async/http_wait;superseded HTTP 成员先 park(内存 fanout 表 + async-trigger store 的 steerParkedBy 后继链),组内真 final 落盘后把合并 正文 fan-out 给每个 parked 成员(completed 不带 usage、清 convergence 项; wait promise 同步 resolve)。 - dashboard-ipc-server:daemon 在 superseded→real-final 之间重启时, trigger-result 沿 steerParkedBy 链找到首个终态后继并镜像(completed 镜像 正文、failed 镜像终态证据),链仍 pending 则继续 running。 - async-trigger-store:新增 recordSteerParked + PersistedAsyncTriggerResult. steerParkedBy(仅 pending、严格 schema 校验、completed/failed 强证据不覆写)。 非 codex-app CLI 上该 flag 为 no-op(既有 type-ahead 队列语义不变);不新增 公开路由,core-only 信任面不变(同租户自己的 turn)。 测试:trigger-api 校验 3 例;trigger-session live/dormant/无 flag plumbing 3 例;bridge R4/R5 守卫更新(http_wait 不再拒绝)+ http_async/http_wait 全组 fan-out 2 例;async-trigger-store park/链/schema 4 例。 Co-Authored-By: Claude Code <noreply@anthropic.com>
review (#1445) minor-1:followSteerParkedChain 原来 8 跳封顶,daemon 在 >8 成员的 steer 组中段重启时,早期成员沿链走不到终态(session open 一直 running、 closed 落 no_output)。成员数本就无界,跳数限制只在重启兜底路径上误伤。 - 链遍历从 dashboard-ipc-server 下沉为 async-trigger-store 导出函数,去掉跳数 上限,改用 visited 集合保证终止:FIFO 后继天然互异且更晚,revisit 只可能是 盘上损坏的环,遇环 fail-closed(undefined → 继续 running,不空转) - 缺 hop / 链尾仍 pending 同样返回 undefined(running 语义不变) - 测试 +4(12 成员长链、损坏环、缺 hop/链尾 pending、failed 终态后继), 原有 N 链用例改调真实导出函数 - 文档补充两个已接受边界:http_wait 纯内存无 durable(wait 既有契约);后继 指针若指向交错的非 steer 普通 turn,parked 成员镜像其 final(归因合理) Co-Authored-By: Claude Code <noreply@anthropic.com>
|
自动评审结论:设计与实现整体成立,无阻断性问题,有 1 个建议合入前补齐的测试缺口,加 2 处文档/命名小修。以下为初步意见,最终以维护者审阅为准。 建议合入前处理1. 重启保险的 poll 镜像路径缺少路由级测试
以及 owner 回退( 2. 公开 API 文档缺
3. 拒绝码字符串语义已过时(零风险改名)
可选(不阻塞合入)
验证情况
|
双审 (#1445) 收敛后的 follow-up,无行为面变更: - F1:新增 trigger-result 路由级测试 6 例,钉住 daemon 重启于 superseded→真 final 窗口时 buildAsyncTriggerLookupResponse 沿 steerParkedBy 链的三态镜像(completed 合并不带 usage / turn_terminal 镜像 provider code / 其余 dispatch_unknown)、链仍 pending 与盘损环 fail-soft 保持 running;此前该镜像胶水整块删除测试仍全绿。 - 镜像 strict/durable 写入包 try/catch:EIO/owner 不符时本次 poll 保持 running 不返 500,下次 poll 重试(与既有 postBarrierFault 收敛同形), 由 writefault 用例变异钉住。 - N3:onCodexAppLedgerDrained 单一收口 prune 失败形态残留的内存 park 表 (worker-pool 导出 pruneSteerFanoutState,daemon 收口调用),钉一例: drain 后迟到的真 final 不得再 fanout 给已走自收敛路径的 parked 成员。 - N1:superseded 拒绝码改名 not_plain_lark_… → not_authorized_sink… (http_async/http_wait 已合法化,原名误导;全仓仅一处日志引用)。 - 注释/文档:fanOutSteerGroupFinal 写明组身份依赖的「同 generation 无旁路 派发」FIFO 不变量;recordSteerParked 写明 lark 后继断链已接受边界; docs-site 中英 api-task-trigger 补 options.steer 字段行与 §5.5;设计 文档增补节同步 fail-soft/prune/两条边界的最终口径。 验证:tsc 0、build 过;新增 7 例对「无镜像块」4 红、对「无 fail-soft」 1 红、prune no-op 1 红,恢复全绿;相关回归 1080+ 全绿。 Co-Authored-By: Claude Code <noreply@anthropic.com>
背景
core-only 公开面(
POST /api/trigger+ 两条 poll 路由)此前无法使用 codex app-server 的原生turn/steer:忙时把消息注入当前活跃 turn(而不是串行排队等它结束)的授权(codexAppSteerable)只在飞书 plain-human 入口计算(computeCodexAppSteerable),HTTP 触发路径从不透传。headless 调用方(如 riff core-only)在任务运行中发追问只能排队等当前轮跑完再起下一轮。契约
POST /api/trigger的options新增steer?: boolean(默认 false,与dryRun互斥);响应在实际受理(非幂等复用)时回显steer: true。canSteer要求 root 与 follow-up head 都显式授权)。turn/steer注入活跃 turn;无法注入(无活跃 turn / closing / review·compact turn / 休眠冷启 / 非 codex-app CLI)时静默降级为普通排队 follow-up,受理不因 steer 报错。结果归因(steer 组)
codex 对合并的 steer 组只产出一条合并 final:runner 给前 N−1 个成员发空的
steer_supersededfinal,最后一个成员(=最新 triggerId)拿真 final + usage。http_async/http_wait,本 PR 新增):superseded 成员先 park(内存 fanout 表 + async-trigger store 的steerParkedBy后继指针链,仅 http_async 写 durable);真 final 落盘后把同一份合并正文 fan-out 给组内每个 parked 成员(wait promise resolve / async result completed +recordCompleted,不带 usage——用量只记最新 trigger),并清idempotentAsyncTurns收敛项,避免随后优雅退出把已合并轮误判失败。steerParkedBy链走到首个终态后继并镜像(completed 镜像正文、failed 镜像终态证据);链仍 pending 则继续running。安全面
不新增公开路由,core-only 仍是 loopback 的「驱动我自己的 turn」面;steer 只授权注入同一租户自己的活跃 turn。R4/R5 的 superseded 守卫从仅
lark扩到lark | http_async | http_wait,VC / doc_comment / suppressed / sink 缺失 / 唯一 head 仍一律 ACK=false 拒绝。测试
trigger-api:steer 校验 3 例(布尔类型、与 dryRun 互斥、fresh/follow-up 受理)trigger-session-turn-idempotency:live/dormant/无 flag plumbing 3 例bridge-final-output-retry:R4/R5 守卫矩阵更新(http_wait 不再拒绝)+ http_async / http_wait 全组 fan-out 2 例async-trigger-store:park / N 链 / 终态强证据不覆写 / 严格 schema 4 例设计文档:
docs/design/2026-07-30-api-only-core-only-bot-mode.md增补一节。🤖 Generated with Claude Code