Inbox Workflow Test Plan
Scope
This document tracks cross-command scenarios where the main value is the interaction between multiple inbox subcommands.
All examples assume:
- isolated temp database
inbox --db TMPDIR/coord.db --json initalready executed- assertions follow the shared rules in ../_shared/README.md
case: thread-lifecycle-happy-path
用例意义
验证 send -> fetch -> claim -> update(in_progress) -> update(blocked) -> reply -> done -> show 的主干链路可用,且线程与消息历史一致。
前置条件
- 空数据库已完成
init - 发送方为
leader - 执行方为
worker-a
输入
inbox --db TMPDIR/coord.db --json send --from leader --to worker-a --subject "Implement feature X" --summary "Add retry policy" --body "Implement retry handling for the HTTP client." --run run_blog_001 --task T1
inbox --db TMPDIR/coord.db --json fetch --agent worker-a --status pending
inbox --db TMPDIR/coord.db --json claim --agent worker-a --thread THREAD_ID --lease-seconds 300
inbox --db TMPDIR/coord.db --json update --agent worker-a --thread THREAD_ID --status in_progress --summary "Implementation started" --body "Scanning current HTTP client usage."
inbox --db TMPDIR/coord.db --json update --agent worker-a --thread THREAD_ID --status blocked --summary "Need timeout decision" --payload-json '{"question":"Should retries apply to read timeouts?"}'
inbox --db TMPDIR/coord.db --json reply --from leader --to worker-a --thread THREAD_ID --summary "Retry read timeouts" --body "Yes, include read timeouts in the retry policy."
inbox --db TMPDIR/coord.db --json done --agent worker-a --thread THREAD_ID --summary "Retry policy implemented" --body "The HTTP client now retries the selected transient failures."
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
预期输出
send返回新建线程,线程状态为pendingfetch返回唯一匹配线程claim后线程状态为claimed- 第一次
update后线程状态为in_progress - 第二次
update后线程状态为blocked reply返回一条kind=answer的消息done后线程状态为doneshow返回线程状态done,并包含完整消息历史
断言结论
- 全链路所有命令退出码为
0 show.data.thread.status == "done"show.data.messages长度为6- 历史中的状态推进顺序与执行顺序一致,不出现丢消息或状态回退
case: blocked-question-reply-resume-to-done
用例意义
验证被阻塞线程在收到答复后可以继续推进,并最终进入完成态。
前置条件
- 已存在由
leader发给worker-c的线程 worker-c已经成功claim
输入
inbox --db TMPDIR/coord.db --json update --agent worker-c --thread THREAD_ID --status blocked --summary "Need policy decision" --body "Should guest users be redirected to login or shown a 403 page?"
inbox --db TMPDIR/coord.db --agent worker-c --json wait-reply --thread THREAD_ID --after-message BLOCKED_MESSAGE_ID --timeout-seconds 2
inbox --db TMPDIR/coord.db --json reply --from leader --to worker-c --thread THREAD_ID --summary "Redirect to login" --body "Redirect guests to login for the MVP."
inbox --db TMPDIR/coord.db --json done --agent worker-c --thread THREAD_ID --summary "Policy applied" --body "The flow now redirects guests to login."
预期输出
update将线程推进到blockedwait-reply在答复出现后唤醒- 唤醒结果包含答复消息
done成功将线程推进到done
断言结论
wait-reply.data.woke == truewait-reply.data.message.kind == "answer"- 最终
done.data.thread.status == "done" - 该用例强调“阻塞后可恢复”,不是单纯验证 reply 本身
case: fail-lifecycle-from-claim-to-terminal
用例意义
验证线程在被领取后可以直接进入失败终态,并且 show 对终态读取一致。
前置条件
- 空数据库已完成
init leader已向worker-b发送任务worker-b已claim该线程
输入
inbox --db TMPDIR/coord.db --json fail --agent worker-b --thread THREAD_ID --summary "Migration failed" --body "The migration cannot proceed because the prior schema is inconsistent."
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
预期输出
fail返回线程状态failedshow返回相同终态
断言结论
fail.data.thread.status == "failed"show.data.thread.status == "failed"- 失败消息保留在线程历史中,可被后续排障读取
case: cancel-lifecycle-after-worker-claim
用例意义
验证线程在执行者已领取后,发起方仍可以取消任务,并进入 cancelled 终态。
前置条件
leader已向worker-c发送任务worker-c已成功claim
输入
inbox --db TMPDIR/coord.db --json cancel --agent leader --thread THREAD_ID --reason "Task superseded by a larger refactor"
预期输出
cancel成功- 返回线程状态
cancelled - 返回的消息记录取消原因
断言结论
cancel.data.thread.status == "cancelled"- 取消属于终态转换,不要求执行者先主动释放 lease
- 原因字段可被后续
show或审计场景消费
case: watch-wakes-then-fetch-sees-new-thread
用例意义
验证 watch 的等待语义与 fetch --unread 的可见性一致,确保新线程到达时执行者既会被唤醒,也能随后拉到未读任务。
前置条件
worker-d尚无匹配pending线程watch先于send启动
输入
inbox --db TMPDIR/coord.db --json watch --agent worker-d --status pending --timeout-seconds 2
inbox --db TMPDIR/coord.db --json send --from leader --to worker-d --subject "Build admin editor" --summary "Create the first editor screen" --body-file TMPDIR/task.md --artifact TMPDIR/task.md --artifact-kind brief --artifact-metadata-json '{"label":"task-brief"}' --run run_blog_004 --task T4
inbox --db TMPDIR/coord.db --json fetch --agent worker-d --status pending --unread
预期输出
watch因新线程到达而唤醒- 唤醒结果中的
thread_id与send返回值一致 - 随后
fetch --unread仍能看到该pending线程
断言结论
watch.data.woke == truewatch.data.thread.thread_id == send.data.thread.thread_idfetch.data.threads长度为1watch唤醒不应提前消费掉线程的未读可见性
case: artifact-visible-through-send-and-show
用例意义
验证 send 写入的 body-file 与 artifact 信息能被后续 show 完整读回。
前置条件
TMPDIR/task.md已存在,内容为测试任务正文- 空数据库已完成
init
输入
inbox --db TMPDIR/coord.db --json send --from leader --to worker-d --subject "Build admin editor" --summary "Create the first editor screen" --body-file TMPDIR/task.md --artifact TMPDIR/task.md --artifact-kind brief --artifact-metadata-json '{"label":"task-brief"}' --run run_blog_004 --task T4
inbox --db TMPDIR/coord.db --json show --thread THREAD_ID
预期输出
send成功创建线程并附带一条 artifactshow的首条消息包含从文件读取的正文与 artifact 列表
断言结论
- 首条消息
body等于TMPDIR/task.md的文件内容 - 首条消息
artifacts长度为1 - 首个 artifact 的
path等于TMPDIR/task.md - 首个 artifact 的
kind等于brief
case: unread-clears-after-mark-read-and-reappears-on-new-message
用例意义
验证 read cursor 的最关键用户感知行为:未读任务可被显式清空,并会在同线程新消息到达后重新出现。
前置条件
leader已向worker-e发送一个pending线程
输入
inbox --db TMPDIR/coord.db --json fetch --agent worker-e --status pending --unread
inbox --db TMPDIR/coord.db --agent worker-e --json show --thread THREAD_ID --mark-read
inbox --db TMPDIR/coord.db --json fetch --agent worker-e --status pending --unread
inbox --db TMPDIR/coord.db --json send --from leader --to worker-e --thread THREAD_ID --summary "Use sentence case" --body "Keep the nav labels in sentence case."
inbox --db TMPDIR/coord.db --json fetch --agent worker-e --status pending --unread
预期输出
- 第一次
fetch --unread返回该线程 show --mark-read成功推进worker-e的 read cursor- 第二次
fetch --unread无匹配结果 - 新消息追加后,第三次
fetch --unread再次返回该线程
断言结论
- 第一次
fetch返回 1 条线程 - 第二次
fetch退出码为10,错误码为no_matching_work - 追加消息后第三次
fetch再次返回 1 条线程 - 未读状态是按 agent 视角计算,而不是线程级布尔值
case: wait-reply-clears-blocked-unread-for-agent
用例意义
验证等待答复的消费者在收到答复后,其阻塞线程未读状态会被消费,避免“已经处理过回复但列表仍显示未读”的错觉。
前置条件
worker-c已拥有一个blocked线程- 该线程阻塞消息对应的
message_id已知 worker-c使用wait-reply等待答复
输入
inbox --db TMPDIR/coord.db --agent worker-c --json wait-reply --thread THREAD_ID --after-message BLOCKED_MESSAGE_ID --timeout-seconds 2
inbox --db TMPDIR/coord.db --json reply --from leader --to worker-c --thread THREAD_ID --summary "Redirect to login" --body "Redirect guests to login for the MVP."
inbox --db TMPDIR/coord.db --agent worker-c --json fetch --status blocked --unread
预期输出
wait-reply在答复后唤醒- 唤醒结果携带
answer消息 - 随后的
fetch --status blocked --unread不再返回该线程
断言结论
wait-reply.data.woke == truewait-reply.data.message.kind == "answer"- 后续
fetch退出码为10 - 对等待中的 agent 来说,答复消费与未读清理是同一条用户契约链路