# 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 init` already executed - assertions follow the shared rules in [../_shared/README.md](../_shared/README.md) ## case: thread-lifecycle-happy-path ### 用例意义 验证 `send -> fetch -> claim -> update(in_progress) -> update(blocked) -> reply -> done -> show` 的主干链路可用,且线程与消息历史一致。 ### 前置条件 - 空数据库已完成 `init` - 发送方为 `leader` - 执行方为 `worker-a` ### 输入 ```bash 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` 返回新建线程,线程状态为 `pending` - `fetch` 返回唯一匹配线程 - `claim` 后线程状态为 `claimed` - 第一次 `update` 后线程状态为 `in_progress` - 第二次 `update` 后线程状态为 `blocked` - `reply` 返回一条 `kind=answer` 的消息 - `done` 后线程状态为 `done` - `show` 返回线程状态 `done`,并包含完整消息历史 ### 断言结论 - 全链路所有命令退出码为 `0` - `show.data.thread.status == "done"` - `show.data.messages` 长度为 `6` - 历史中的状态推进顺序与执行顺序一致,不出现丢消息或状态回退 ## case: blocked-question-reply-resume-to-done ### 用例意义 验证被阻塞线程在收到答复后可以继续推进,并最终进入完成态。 ### 前置条件 - 已存在由 `leader` 发给 `worker-c` 的线程 - `worker-c` 已经成功 `claim` ### 输入 ```bash 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` 将线程推进到 `blocked` - `wait-reply` 在答复出现后唤醒 - 唤醒结果包含答复消息 - `done` 成功将线程推进到 `done` ### 断言结论 - `wait-reply.data.woke == true` - `wait-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` 该线程 ### 输入 ```bash 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` 返回线程状态 `failed` - `show` 返回相同终态 ### 断言结论 - `fail.data.thread.status == "failed"` - `show.data.thread.status == "failed"` - 失败消息保留在线程历史中,可被后续排障读取 ## case: cancel-lifecycle-after-worker-claim ### 用例意义 验证线程在执行者已领取后,发起方仍可以取消任务,并进入 `cancelled` 终态。 ### 前置条件 - `leader` 已向 `worker-c` 发送任务 - `worker-c` 已成功 `claim` ### 输入 ```bash 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` 启动 ### 输入 ```bash 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 == true` - `watch.data.thread.thread_id == send.data.thread.thread_id` - `fetch.data.threads` 长度为 `1` - `watch` 唤醒不应提前消费掉线程的未读可见性 ## case: artifact-visible-through-send-and-show ### 用例意义 验证 `send` 写入的 body-file 与 artifact 信息能被后续 `show` 完整读回。 ### 前置条件 - `TMPDIR/task.md` 已存在,内容为测试任务正文 - 空数据库已完成 `init` ### 输入 ```bash 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` 成功创建线程并附带一条 artifact - `show` 的首条消息包含从文件读取的正文与 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` 线程 ### 输入 ```bash 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` 等待答复 ### 输入 ```bash 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 == true` - `wait-reply.data.message.kind == "answer"` - 后续 `fetch` 退出码为 `10` - 对等待中的 agent 来说,答复消费与未读清理是同一条用户契约链路