Files
ai-workflow-skill/docs/tests/inbox/workflows/README.md
T

9.8 KiB

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

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 返回新建线程,线程状态为 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

输入

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-bclaim 该线程

输入

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

输入

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_idsend 返回值一致
  • 随后 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

输入

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 线程

输入

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 == true
  • wait-reply.data.message.kind == "answer"
  • 后续 fetch 退出码为 10
  • 对等待中的 agent 来说,答复消费与未读清理是同一条用户契约链路