@team-agent/installer 0.5.111 → 0.5.113

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +93 -779
  4. package/crates/team-agent/src/cli/diagnose.rs +2 -424
  5. package/crates/team-agent/src/cli/emit.rs +158 -373
  6. package/crates/team-agent/src/cli/leaders.rs +32 -32
  7. package/crates/team-agent/src/cli/mod.rs +8 -15
  8. package/crates/team-agent/src/cli/named_address.rs +17 -16
  9. package/crates/team-agent/src/cli/route.rs +4 -4
  10. package/crates/team-agent/src/cli/send/presentation.rs +19 -18
  11. package/crates/team-agent/src/cli/send/resolve.rs +11 -11
  12. package/crates/team-agent/src/cli/send.rs +4 -4
  13. package/crates/team-agent/src/cli/spec.rs +29 -32
  14. package/crates/team-agent/src/cli/status.rs +7 -7
  15. package/crates/team-agent/src/cli/tests/base.rs +6 -6
  16. package/crates/team-agent/src/cli/tests/divergence.rs +3 -2
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +1 -1
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +2 -86
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +15 -140
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +1 -258
  21. package/crates/team-agent/src/cli/tests/mod.rs +0 -2
  22. package/crates/team-agent/src/cli/tests/named_address.rs +3 -3
  23. package/crates/team-agent/src/cli/tests/status_send.rs +7 -7
  24. package/crates/team-agent/src/cli/triage.rs +53 -2
  25. package/crates/team-agent/src/cli/types.rs +12 -116
  26. package/crates/team-agent/src/compiler/tests.rs +12 -0
  27. package/crates/team-agent/src/compiler.rs +0 -9
  28. package/crates/team-agent/src/coordinator/health.rs +3 -283
  29. package/crates/team-agent/src/coordinator/mod.rs +0 -5
  30. package/crates/team-agent/src/coordinator/tests/basics.rs +0 -9
  31. package/crates/team-agent/src/coordinator/tests/mod.rs +0 -1
  32. package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -17
  33. package/crates/team-agent/src/coordinator/types.rs +1 -21
  34. package/crates/team-agent/src/db/message_store.rs +0 -42
  35. package/crates/team-agent/src/db/migration.rs +49 -9
  36. package/crates/team-agent/src/db/schema.rs +27 -20
  37. package/crates/team-agent/src/leader/owner_bind.rs +0 -24
  38. package/crates/team-agent/src/leader/start.rs +5 -0
  39. package/crates/team-agent/src/leader/tests/byte_findings.rs +0 -20
  40. package/crates/team-agent/src/leader/tests/identity.rs +21 -32
  41. package/crates/team-agent/src/leader/tests/lease_claim.rs +0 -55
  42. package/crates/team-agent/src/lifecycle/launch/identity.rs +172 -13
  43. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +325 -14
  44. package/crates/team-agent/src/lifecycle/launch.rs +2 -2
  45. package/crates/team-agent/src/lifecycle/profile_launch.rs +58 -46
  46. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +97 -52
  47. package/crates/team-agent/src/lifecycle/restart.rs +1 -1
  48. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -51
  49. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +3 -2
  50. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +23 -23
  51. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +29 -55
  52. package/crates/team-agent/src/messaging/mod.rs +0 -2
  53. package/crates/team-agent/src/messaging/results.rs +0 -58
  54. package/crates/team-agent/src/messaging/tests/runtime.rs +0 -46
  55. package/crates/team-agent/src/provider/adapter.rs +8 -59
  56. package/crates/team-agent/src/provider/adapters/claude.rs +6 -14
  57. package/crates/team-agent/src/provider/adapters/codex.rs +8 -18
  58. package/crates/team-agent/src/provider/adapters/copilot.rs +5 -22
  59. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +8 -45
  60. package/crates/team-agent/src/provider/adapters/grok.rs +7 -31
  61. package/crates/team-agent/src/provider/adapters/pi.rs +7 -18
  62. package/crates/team-agent/src/provider/command_helpers.rs +45 -0
  63. package/crates/team-agent/src/provider/mod.rs +1 -0
  64. package/crates/team-agent/src/provider/model_catalog.rs +21 -28
  65. package/crates/team-agent/src/provider/tests/adapter.rs +70 -0
  66. package/crates/team-agent/src/provider/tests/command_helpers.rs +55 -0
  67. package/crates/team-agent/src/state/repository.rs +1 -5
  68. package/package.json +4 -4
  69. package/templates/team_state.md +1 -1
  70. package/crates/team-agent/src/cli/tests/peer_allow.rs +0 -164
  71. package/crates/team-agent/src/cli/tests/verb_validate.rs +0 -195
  72. package/crates/team-agent/src/coordinator/tests/watch.rs +0 -223
  73. package/crates/team-agent/src/messaging/peers.rs +0 -34
@@ -49,7 +49,6 @@ pub(crate) enum DispatchKind {
49
49
  Leaders,
50
50
  Doctor,
51
51
  Approvals,
52
- AllowPeerTalk,
53
52
  Route,
54
53
  Profile,
55
54
  InstallSkill,
@@ -75,7 +74,6 @@ pub(crate) const ALL_DISPATCH_KINDS: &[DispatchKind] = &[
75
74
  DispatchKind::Leaders,
76
75
  DispatchKind::Doctor,
77
76
  DispatchKind::Approvals,
78
- DispatchKind::AllowPeerTalk,
79
77
  DispatchKind::Route,
80
78
  DispatchKind::Profile,
81
79
  DispatchKind::InstallSkill,
@@ -108,36 +106,35 @@ pub(crate) struct CommandSpec {
108
106
  // Only human operations belong here. Compatibility commands are private in emit.rs.
109
107
  #[rustfmt::skip]
110
108
  pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
111
- CommandSpec { name: "quick-start", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::QuickStart), summary: "从角色文件启动队伍,或查看已有队伍的连接方式", usage: "team-agent quick-start [TEAMDIR] [--workspace WORKSPACE] [--team TEAM] [--name NAME] [--team-id TEAM] [--yes] [--backend tmux|conpty] [--json] [--detail]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
112
- CommandSpec { name: "send", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::Send), summary: "向指定队友派发任务", usage: "team-agent send <agent> MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--mailbox] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: Some("后续兼容版本"), action: Some("使用 team-agent send <agent> '任务内容',用 --workspace 和 --team 选择队伍"), governance: None },
113
- CommandSpec { name: "status", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::Status), summary: "查看队友状态,不读取队友的对话内容", usage: "team-agent status [<agent>] [--workspace WORKSPACE] [--team TEAM] [--json] [--summary|--detail]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
114
- CommandSpec { name: "models", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::Models), summary: "查找工具实际支持的模型名称", usage: "team-agent models [--provider pi|cursor_agent|codex|claude|claude_code|grok] [QUERY|--search TEXT] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
115
- CommandSpec { name: "inbox", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::Inbox), summary: "查看队友回复", usage: "team-agent inbox <agent> [-n N|--limit N] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
116
- CommandSpec { name: "restart", tier: CommandTier::Core, category: CommandCategory::TeamLifecycle, kind: CommandKind::Dispatch(DispatchKind::Restart), summary: "恢复已有队伍,优先沿用已保存的会话", usage: "team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--session-converge-deadline SECONDS] [--json] [--detail]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
117
- CommandSpec { name: "shutdown", tier: CommandTier::Core, category: CommandCategory::TeamLifecycle, kind: CommandKind::Dispatch(DispatchKind::Shutdown), summary: "关闭指定队伍,并查看是否仍有残留资源", usage: "team-agent shutdown [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
118
- CommandSpec { name: "add-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::AddAgent), summary: "新增并启动一位队友", usage: "team-agent add-agent <agent> [--role-file FILE] [--provider TOOL] [--model MODEL] [--effort LEVEL] [--bypass true|false] [--prompt TEXT] [--profile NAME] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
119
- CommandSpec { name: "start-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::StartAgent), summary: "启动队伍中已有的队友", usage: "team-agent start-agent <agent> [--provider TOOL] [--model MODEL] [--effort LEVEL] [--bypass true|false] [--prompt TEXT] [--profile NAME] [--workspace WORKSPACE] [--team TEAM] [--allow-fresh] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
120
- CommandSpec { name: "stop-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::StopAgent), summary: "暂停一位队友,保留其配置和会话记录", usage: "team-agent stop-agent <agent> [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
121
- CommandSpec { name: "reset-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::ResetAgent), summary: "清除队友保存的会话关联", usage: "team-agent reset-agent <agent> --discard-session [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
122
- CommandSpec { name: "clone-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::CloneAgent), summary: "复制队友配置,创建新队友", usage: "team-agent clone-agent <agent> --as NEW_AGENT [--label LABEL] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
123
- CommandSpec { name: "fork-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::ForkAgent), summary: "在工具支持的条件下从队友会话分出新队友", usage: "team-agent fork-agent <agent> --as NEW_AGENT [--label LABEL] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
124
- CommandSpec { name: "remove-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::RemoveAgent), summary: "确认后移除一位队友", usage: "team-agent remove-agent <agent> --confirm [--workspace WORKSPACE] [--team TEAM] [--from-spec] [--force] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
125
- CommandSpec { name: "leaders", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Leaders), summary: "查看本机可用的主控及其所属队伍", usage: "team-agent leaders [QUERY|--search TEXT] [--all|--stale] [--json] | --prune [--dry-run] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
126
- CommandSpec { name: "doctor", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Doctor), summary: "检查队伍并给出恢复建议,默认不修复", usage: "team-agent doctor [SPEC] [--workspace WORKSPACE] [--team TEAM] [--comms] [--gate orphans|comms] [--fix] [--fix-schema] [--cleanup-orphans] [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
127
- CommandSpec { name: "approvals", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Approvals), summary: "查看队友正在等待的权限确认", usage: "team-agent approvals [<agent>] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
128
- CommandSpec { name: "allow-peer-talk", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::AllowPeerTalk), summary: "允许两位队友直接交流", usage: "team-agent allow-peer-talk <agent> OTHER_AGENT [--workspace WORKSPACE] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
129
- CommandSpec { name: "route", tier: CommandTier::Core, category: CommandCategory::Setup, kind: CommandKind::Dispatch(DispatchKind::Route), summary: "设置工具启动时额外使用的命令行参数", usage: "team-agent route [status|enable|disable|show [TOOL]|set TOOL -- ARG...|add TOOL -- ARG...|clear TOOL] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
130
- CommandSpec { name: "profile", tier: CommandTier::Core, category: CommandCategory::Setup, kind: CommandKind::Dispatch(DispatchKind::Profile), summary: "管理登录方式和代理设置", usage: "team-agent profile init|doctor|show NAME [--workspace WORKSPACE] [--team TEAM] [--auth-mode MODE] [--proxy-mode direct|inherit] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
131
- CommandSpec { name: "install-skill", tier: CommandTier::Core, category: CommandCategory::Setup, kind: CommandKind::Dispatch(DispatchKind::InstallSkill), summary: "可选:安装已有的公开操作指南", usage: "team-agent install-skill (--source DIR|--uninstall) [--target codex|claude|copilot|all] [--dest DIR] [--dry-run] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
132
- CommandSpec { name: "claim-leader", tier: CommandTier::Guided, category: CommandCategory::GuidedRecovery, kind: CommandKind::Dispatch(DispatchKind::ClaimLeader), summary: "仅按体检提示,将当前终端登记为主控", usage: "team-agent claim-leader [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json] [--detail]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
133
- CommandSpec { name: "takeover", tier: CommandTier::Guided, category: CommandCategory::GuidedRecovery, kind: CommandKind::Dispatch(DispatchKind::Takeover), summary: "仅按体检提示,确认后接管现有队伍", usage: "team-agent takeover [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
134
- CommandSpec { name: "attach-leader", tier: CommandTier::Guided, category: CommandCategory::GuidedRecovery, kind: CommandKind::Dispatch(DispatchKind::AttachLeader), summary: "仅按体检提示,将已核验的终端连接为主控", usage: "team-agent attach-leader [--workspace WORKSPACE] [--team TEAM] --pane PANE --provider TOOL [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
135
- CommandSpec { name: "pi", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "pi" }, summary: "用 Pi 打开主控对话", usage: "team-agent pi [--attach-existing] [--confirm] [--attach-session SESSION] [--external-leader] [--allow-nested-attach] [--json] [-- TOOL_ARGS...]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
136
- CommandSpec { name: "codex", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "codex" }, summary: "用 Codex 打开主控对话", usage: "team-agent codex [--attach-existing] [--confirm] [--attach-session SESSION] [--external-leader] [--allow-nested-attach] [--json] [-- TOOL_ARGS...]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
137
- CommandSpec { name: "claude", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "claude" }, summary: "用 Claude Code 打开主控对话", usage: "team-agent claude [--attach-existing] [--confirm] [--attach-session SESSION] [--external-leader] [--allow-nested-attach] [--json] [-- TOOL_ARGS...]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
138
- CommandSpec { name: "copilot", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "copilot" }, summary: "用 Copilot 打开主控对话", usage: "team-agent copilot [--attach-existing] [--confirm] [--attach-session SESSION] [--external-leader] [--allow-nested-attach] [--json] [-- TOOL_ARGS...]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
139
- CommandSpec { name: "grok", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "grok" }, summary: "用 Grok 打开主控对话", usage: "team-agent grok [--attach-existing] [--confirm] [--attach-session SESSION] [--external-leader] [--allow-nested-attach] [--json] [-- TOOL_ARGS...]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
140
- CommandSpec { name: "cursor", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "cursor_agent" }, summary: "用 Cursor 的 agent 工具打开主控对话", usage: "team-agent cursor [--attach-existing] [--confirm] [--attach-session SESSION] [--external-leader] [--allow-nested-attach] [--json] [-- TOOL_ARGS...]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
109
+ CommandSpec { name: "quick-start", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::QuickStart), summary: "Create a team from role files; use restart for an initialized team", usage: "team-agent quick-start [TEAMDIR] [--workspace WORKSPACE] [--team TEAM] [--name NAME] [--team-id TEAM] [--yes] [--backend tmux|conpty] [--json] [--detail]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
110
+ CommandSpec { name: "send", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::Send), summary: "Send a task to a specified agent", usage: "team-agent send <agent> MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--mailbox] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: Some("a future compatibility release"), action: Some("Use team-agent send <agent> 'task message'; select scope with --workspace and --team"), governance: None },
111
+ CommandSpec { name: "status", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::Status), summary: "Check agent status without reading conversation content", usage: "team-agent status [<agent>] [--workspace WORKSPACE] [--team TEAM] [--json] [--summary|--detail]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
112
+ CommandSpec { name: "models", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::Models), summary: "Discover model names supported by the native provider", usage: "team-agent models [--provider pi|cursor_agent|codex|claude|claude_code|grok] [QUERY|--search TEXT] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
113
+ CommandSpec { name: "inbox", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::Inbox), summary: "Read agent replies", usage: "team-agent inbox <agent> [-n N|--limit N] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
114
+ CommandSpec { name: "restart", tier: CommandTier::Core, category: CommandCategory::TeamLifecycle, kind: CommandKind::Dispatch(DispatchKind::Restart), summary: "Restart an existing team using saved sessions when available", usage: "team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--session-converge-deadline SECONDS] [--json] [--detail]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
115
+ CommandSpec { name: "shutdown", tier: CommandTier::Core, category: CommandCategory::TeamLifecycle, kind: CommandKind::Dispatch(DispatchKind::Shutdown), summary: "Shut down the selected team and report residual resources", usage: "team-agent shutdown [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
116
+ CommandSpec { name: "add-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::AddAgent), summary: "Add and start an agent", usage: "team-agent add-agent <agent> [--role-file FILE] [--provider TOOL] [--model MODEL] [--effort LEVEL] [--bypass true|false] [--prompt TEXT] [--profile NAME] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
117
+ CommandSpec { name: "start-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::StartAgent), summary: "Start an existing team agent", usage: "team-agent start-agent <agent> [--provider TOOL] [--model MODEL] [--effort LEVEL] [--bypass true|false] [--prompt TEXT] [--profile NAME] [--workspace WORKSPACE] [--team TEAM] [--allow-fresh] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
118
+ CommandSpec { name: "stop-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::StopAgent), summary: "Stop an agent while retaining configuration and session history", usage: "team-agent stop-agent <agent> [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
119
+ CommandSpec { name: "reset-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::ResetAgent), summary: "Discard an agent's saved session association", usage: "team-agent reset-agent <agent> --discard-session [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
120
+ CommandSpec { name: "clone-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::CloneAgent), summary: "Clone agent configuration into a new agent", usage: "team-agent clone-agent <agent> --as NEW_AGENT [--label LABEL] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
121
+ CommandSpec { name: "fork-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::ForkAgent), summary: "Fork an agent session where supported by the provider", usage: "team-agent fork-agent <agent> --as NEW_AGENT [--label LABEL] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
122
+ CommandSpec { name: "remove-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::RemoveAgent), summary: "Remove an agent after confirmation", usage: "team-agent remove-agent <agent> --confirm [--workspace WORKSPACE] [--team TEAM] [--from-spec] [--force] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
123
+ CommandSpec { name: "leaders", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Leaders), summary: "List local leaders and their team scopes", usage: "team-agent leaders [QUERY|--search TEXT] [--all|--stale] [--json] | --prune [--dry-run] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
124
+ CommandSpec { name: "doctor", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Doctor), summary: "Diagnose a team and suggest recovery; read-only by default", usage: "team-agent doctor [SPEC] [--workspace WORKSPACE] [--team TEAM] [--comms] [--gate orphans|comms] [--fix] [--fix-schema] [--cleanup-orphans] [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
125
+ CommandSpec { name: "approvals", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Approvals), summary: "List pending agent approvals", usage: "team-agent approvals [<agent>] [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
126
+ CommandSpec { name: "route", tier: CommandTier::Core, category: CommandCategory::Setup, kind: CommandKind::Dispatch(DispatchKind::Route), summary: "Configure additional provider launch arguments", usage: "team-agent route [status|enable|disable|show [TOOL]|set TOOL -- ARG...|add TOOL -- ARG...|clear TOOL] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
127
+ CommandSpec { name: "profile", tier: CommandTier::Core, category: CommandCategory::Setup, kind: CommandKind::Dispatch(DispatchKind::Profile), summary: "Manage authentication and proxy profiles", usage: "team-agent profile init|doctor|show NAME [--workspace WORKSPACE] [--team TEAM] [--auth-mode MODE] [--proxy-mode direct|inherit] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
128
+ CommandSpec { name: "install-skill", tier: CommandTier::Core, category: CommandCategory::Setup, kind: CommandKind::Dispatch(DispatchKind::InstallSkill), summary: "Optionally install an existing public operation skill", usage: "team-agent install-skill (--source DIR|--uninstall) [--target codex|claude|copilot|all] [--dest DIR] [--dry-run] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
129
+ CommandSpec { name: "claim-leader", tier: CommandTier::Guided, category: CommandCategory::GuidedRecovery, kind: CommandKind::Dispatch(DispatchKind::ClaimLeader), summary: "Register the current terminal as leader only when diagnosis recommends it", usage: "team-agent claim-leader [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json] [--detail]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
130
+ CommandSpec { name: "takeover", tier: CommandTier::Guided, category: CommandCategory::GuidedRecovery, kind: CommandKind::Dispatch(DispatchKind::Takeover), summary: "Take over an existing team only after diagnosis and confirmation", usage: "team-agent takeover [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
131
+ CommandSpec { name: "attach-leader", tier: CommandTier::Guided, category: CommandCategory::GuidedRecovery, kind: CommandKind::Dispatch(DispatchKind::AttachLeader), summary: "Attach a verified leader terminal only when diagnosis recommends it", usage: "team-agent attach-leader [--workspace WORKSPACE] [--team TEAM] --pane PANE --provider TOOL [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
132
+ CommandSpec { name: "pi", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "pi" }, summary: "Open a leader session with Pi", usage: "team-agent pi [--attach-existing] [--confirm] [--attach-session SESSION] [--external-leader] [--allow-nested-attach] [--json] [-- TOOL_ARGS...]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
133
+ CommandSpec { name: "codex", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "codex" }, summary: "Open a leader session with Codex", usage: "team-agent codex [--attach-existing] [--confirm] [--attach-session SESSION] [--external-leader] [--allow-nested-attach] [--json] [-- TOOL_ARGS...]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
134
+ CommandSpec { name: "claude", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "claude" }, summary: "Open a leader session with Claude Code", usage: "team-agent claude [--attach-existing] [--confirm] [--attach-session SESSION] [--external-leader] [--allow-nested-attach] [--json] [-- TOOL_ARGS...]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
135
+ CommandSpec { name: "copilot", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "copilot" }, summary: "Open a leader session with Copilot", usage: "team-agent copilot [--attach-existing] [--confirm] [--attach-session SESSION] [--external-leader] [--allow-nested-attach] [--json] [-- TOOL_ARGS...]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
136
+ CommandSpec { name: "grok", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "grok" }, summary: "Open a leader session with Grok", usage: "team-agent grok [--attach-existing] [--confirm] [--attach-session SESSION] [--external-leader] [--allow-nested-attach] [--json] [-- TOOL_ARGS...]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
137
+ CommandSpec { name: "cursor", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "cursor_agent" }, summary: "Open a leader session with Cursor agent", usage: "team-agent cursor [--attach-existing] [--confirm] [--attach-session SESSION] [--external-leader] [--allow-nested-attach] [--json] [-- TOOL_ARGS...]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
141
138
  ];
142
139
 
143
140
  pub(crate) fn command_spec(name: &str) -> Option<&'static CommandSpec> {
@@ -209,7 +209,7 @@ fn csv_agent_status(
209
209
  health: Option<&serde_json::Map<String, Value>>,
210
210
  ) -> &'static str {
211
211
  if stale_agent_bucket(agent).is_some() {
212
- return "错误";
212
+ return "error";
213
213
  }
214
214
  let raw = agent
215
215
  .get("status")
@@ -226,7 +226,7 @@ fn csv_agent_status(
226
226
  || matches!(hstatus.as_str(), "failed" | "error" | "spawn_failed")
227
227
  || matches!(raw.as_str(), "stopped" | "done")
228
228
  {
229
- return "错误";
229
+ return "error";
230
230
  }
231
231
  let pane_present = agent
232
232
  .get("pane_id")
@@ -239,21 +239,21 @@ fn csv_agent_status(
239
239
  .filter(|pane| !pane.is_empty())
240
240
  .is_some();
241
241
  if matches!(raw.as_str(), "running" | "busy" | "working") && !pane_present {
242
- return "错误";
242
+ return "error";
243
243
  }
244
244
  // 0.5.35 R4: canonical UNKNOWN beats legacy WORKING in human CSV too.
245
245
  if canonical_worker_state_is_unknown(agent) {
246
- return "未知";
246
+ return "unknown";
247
247
  }
248
248
  if raw == "idle" || hstatus == "idle" {
249
- return "空闲";
249
+ return "idle";
250
250
  }
251
251
  if matches!(raw.as_str(), "running" | "busy" | "working")
252
252
  || matches!(hstatus.as_str(), "running" | "working" | "busy")
253
253
  {
254
- return "工作";
254
+ return "working";
255
255
  }
256
- "错误"
256
+ "error"
257
257
  }
258
258
 
259
259
  /// `_latest_result_line`(`commands.py:333-337`):agent_id/summary/created_at 渲染单行。
@@ -420,7 +420,7 @@ fn format_status_csv_preserves_agent_order_and_collapses_errors() {
420
420
  });
421
421
  assert_eq!(
422
422
  format_status_csv(&data),
423
- "zeta,空闲\nalpha,工作\nerr_failed,错误\nerr_missing_pane,错误\nerr_unknown,错误\nerr_stopped,错误"
423
+ "zeta,idle\nalpha,working\nerr_failed,error\nerr_missing_pane,error\nerr_unknown,error\nerr_stopped,error"
424
424
  );
425
425
  }
426
426
 
@@ -489,7 +489,7 @@ fn cli_error_payload_plain_runtime() {
489
489
  assert_eq!(payload.error, "some other error");
490
490
  assert_eq!(
491
491
  payload.action,
492
- "先运行 team-agent doctor --workspace . 检查所选队伍,或查看此处列出的错误日志。"
492
+ "Run team-agent doctor --workspace . for the selected team, or inspect the error log shown here."
493
493
  );
494
494
  assert_eq!(payload.log, "/tmp/y.log");
495
495
  assert_eq!(payload.reason, None);
@@ -511,7 +511,7 @@ fn cli_error_payload_tmux_conflict_quick_start_enrichment() {
511
511
  // context reset is only through restart --allow-fresh with explicit consent.
512
512
  assert_eq!(
513
513
  payload.action,
514
- "终端会话 `my-team` 已存在,可能是你的已有队伍。恢复用 team-agent restart;只有明确同意丢弃旧对话才用 --allow-fresh。另建队伍请修改 TEAM.md 的 name 后再 quick-start;不要关闭已有队伍凑成功。"
514
+ "Terminal session `my-team` already exists and may belong to your existing team. Resume with team-agent restart; use --allow-fresh only after explicitly accepting loss of saved context. To create another team, change the name in TEAM.md before quick-start. Do not shut down an existing team to bypass this refusal."
515
515
  );
516
516
  let next = payload.next_actions.as_ref().unwrap();
517
517
  assert_eq!(next.len(), 2);
@@ -529,11 +529,11 @@ fn cli_error_payload_tmux_conflict_non_quick_start_enrichment() {
529
529
  assert_eq!(payload.session_name.as_deref(), Some("my-team"));
530
530
  assert_eq!(
531
531
  payload.action,
532
- "终端会话 `my-team` 已存在,可能属于运行中的队伍。不要关闭它;请使用另一个队伍名称后再启动。"
532
+ "Terminal session `my-team` already exists and may belong to a running team. Do not shut it down; choose another team name before starting."
533
533
  );
534
534
  let next = payload.next_actions.as_ref().unwrap();
535
535
  assert_eq!(next.len(), 1);
536
- assert!(next[0].contains("队伍") && next[0].contains("名称"));
536
+ assert!(next[0].contains("team") && next[0].contains("name"));
537
537
  }
538
538
 
539
539
  #[test]
@@ -749,7 +749,7 @@ fn cmd_leader_passthrough_help_is_pure_human_guidance() {
749
749
  let result = cmd_leader_passthrough(provider, &[alias.into()], &cwd).unwrap();
750
750
  assert_eq!(result.exit, ExitCode::Ok);
751
751
  assert!(
752
- matches!(&result.output, CmdOutput::Human(text) if text.contains(provider) && text.contains("用法") && text.contains("Examples") && text.contains("下一步"))
752
+ matches!(&result.output, CmdOutput::Human(text) if text.contains(provider) && text.contains("Usage") && text.contains("Examples") && text.contains("Next Action"))
753
753
  );
754
754
  }
755
755
  }
@@ -457,7 +457,7 @@ fn red_send_target_none_is_not_broadcast() {
457
457
  assert_eq!(send_target(None, Some("*")), MessageTarget::Broadcast);
458
458
  }
459
459
 
460
- // Unified doctor human output is triage only, including the comms gate.
460
+ // Doctor retains bounded triage navigation plus complete diagnostic details.
461
461
  #[test]
462
462
  fn cmd_doctor_comms_human_is_compact_triage_without_boundary_prefix() {
463
463
  let mut args = DoctorArgs {
@@ -483,8 +483,9 @@ fn cmd_doctor_comms_human_is_compact_triage_without_boundary_prefix() {
483
483
  };
484
484
  assert_eq!(text, crate::cli::triage::render("doctor", &report));
485
485
  assert!(text.starts_with("doctor:"));
486
- assert_eq!(text.lines().count(), 1 + report["issues"].as_array().unwrap().len()
486
+ assert_eq!(text.lines().count(), 2 + report["issues"].as_array().unwrap().len()
487
487
  + report["suggested_repairs"].as_array().unwrap().len());
488
+ assert!(text.lines().last().unwrap().starts_with("details: "));
488
489
  }
489
490
 
490
491
  // ---- #13 / #27 (P2): run() must NOT treat 'claude_code' as a passthrough trigger ----
@@ -257,7 +257,7 @@ fn inbox_excludes_leader_stage_result_notifications() {
257
257
  // the deterministic check sub-shapes (run_id is a random uuid; not value-locked). ────────────────
258
258
  #[test]
259
259
  fn comms_selftest_golden_boundary_scope_and_check_shapes() {
260
- let boundary = "这里只检查终端连接是否一致,不实际发送消息,也不证明队友已回复。";
260
+ let boundary = "This checks terminal connection consistency only; it does not send a message or prove that a worker replied.";
261
261
  let ws = tmp_workspace();
262
262
  let v = diagnose_port::comms_selftest(&ws, None, None).expect("comms_selftest");
263
263
  let obj = v.as_object().expect("comms dict");
@@ -100,11 +100,10 @@ fn cli_shutdown_kills_team_session_real_teardown() {
100
100
 
101
101
  // =========================================================================
102
102
  // WAVE-2 Lane B — CLI leader handler delegation byte-parity (leader_port::*).
103
- // The three CLI verbs are thin pass-throughs (cli/commands.py:152-161):
103
+ // The retained CLI verbs are thin pass-throughs (cli/commands.py:152-157):
104
104
  // cmd_takeover -> runtime.takeover(ws, team, confirm)
105
105
  // cmd_claim_leader -> runtime.claim_leader(ws, team, confirm) (Family A)
106
- // cmd_identity -> runtime.leader_identity(ws, team)
107
- // leader_port::{takeover,claim_leader,leader_identity} are STUBS returning
106
+ // leader_port::{takeover,claim_leader} are STUBS returning
108
107
  // the WRONG shape today -> these LOCK the golden dict so the porter wires
109
108
  // them into leader::* / runtime.* and matches byte-for-byte.
110
109
  // Golden re-probed @ team-agent-public (probe_claim.py / probe_rtclaim.py /
@@ -297,89 +296,6 @@ fn leader_port_claim_leader_no_pane_refuses_caller_pane_missing_family_a() {
297
296
  );
298
297
  }
299
298
 
300
- // RED — leader_identity(): CLI directly emits leader.leader_identity's 9-key
301
- // dict (runtime.leader_identity is imported from leader). golden probe_lid.py
302
- // keys: ok, uuid_prefix, machine_fingerprint, workspace_abspath, os_user,
303
- // team_id, current_pane_id, last_seen_at, source. Current stub returns
304
- // {ok:true, team:...} (wrong shape) -> RED.
305
- #[test]
306
- fn leader_port_leader_identity_emits_nine_key_dict() {
307
- let ws = leader_port_ws("identity");
308
- std::fs::create_dir_all(crate::model::paths::runtime_dir(&ws)).unwrap();
309
- let v = super::leader_port::leader_identity(&ws, None).unwrap();
310
- assert_eq!(v["ok"], json!(true));
311
- let obj = v.as_object().expect("identity → JSON object");
312
- for key in [
313
- "ok",
314
- "uuid_prefix",
315
- "machine_fingerprint",
316
- "workspace_abspath",
317
- "os_user",
318
- "team_id",
319
- "current_pane_id",
320
- "last_seen_at",
321
- "source",
322
- ] {
323
- assert!(
324
- obj.contains_key(key),
325
- "golden identity dict must carry '{key}', got {obj:?}"
326
- );
327
- }
328
- // no override/state uuid → source is the leader-plan "derived" string.
329
- assert_eq!(v["source"], json!("derived"));
330
- // uuid_prefix is exactly 12 hex chars (derive[:12]).
331
- let prefix = v["uuid_prefix"].as_str().expect("uuid_prefix str");
332
- assert_eq!(prefix.len(), 12, "uuid_prefix == derived[:12]");
333
- assert!(prefix.chars().all(|c| c.is_ascii_hexdigit()));
334
- // no team registered + no TMUX_PANE/receiver → these are JSON null.
335
- if std::env::var_os("TMUX_PANE").is_none() {
336
- assert_eq!(v["current_pane_id"], serde_json::Value::Null);
337
- }
338
- assert_eq!(v["last_seen_at"], serde_json::Value::Null);
339
- }
340
-
341
- // ── cmd_watch (cli/adapters.rs:58) [RED] — today a CmdResult::none() no-op ───────────────────────
342
- // Golden cmd_watch (cli/commands.py:103-109) DELEGATES to run_watch(workspace.resolve(), team) and
343
- // exits 0. Golden run_watch (watch/__init__.py:25-37) is a `while True` LIVE TAIL that streams
344
- // render_event_line output (collect_watch_lines) for the watched team. The Rust cmd_watch returns
345
- // CmdResult::none() — a no-op that never touches the watch subsystem. The watch LINE SHAPE is itself
346
- // byte-locked by coordinator::render_event_line (coordinator/tests.rs GROUP H); the cli contract here
347
- // is that cmd_watch must DELEGATE and surface those rendered lines. Seeded a result_received event ->
348
- // golden render = "result_received: <agent> -> <summary>" (render_event_line: agent_id + summary[:80]).
349
- //
350
- // RED confirmed TODAY: cmd_watch=none() returns immediately (no watch line) -> the assertion fails
351
- // (and does NOT hang). PORTER: wire cmd_watch -> coordinator::run_watch(resolved workspace, team,
352
- // interval, sink). Since golden's tail is `while True`, the cli port needs a TERMINATING / bounded
353
- // entry to be both byte-parity AND unit-testable (collect the current watch lines into the CmdResult,
354
- // or stream to stdout with a bounded test seam) — a blocking unit test is unacceptable.
355
- #[test]
356
- fn cmd_watch_delegates_and_surfaces_rendered_watch_lines_not_noop() {
357
- let ws = tmp_workspace();
358
- let logs = crate::model::paths::logs_dir(&ws);
359
- std::fs::create_dir_all(&logs).unwrap();
360
- std::fs::write(
361
- logs.join("events.jsonl"),
362
- "{\"event\":\"result_received\",\"agent_id\":\"alpha\",\"summary\":\"did the thing\"}\n",
363
- )
364
- .unwrap();
365
- let r = cmd_watch(&WatchArgs {
366
- workspace: ws.clone(),
367
- team: None,
368
- })
369
- .expect("cmd_watch returns a CmdResult");
370
- let text = match &r.output {
371
- CmdOutput::Human(s) => s.clone(),
372
- CmdOutput::Json(v) => v.to_string(),
373
- CmdOutput::None => String::new(),
374
- };
375
- assert!(
376
- text.contains("result_received: alpha -> did the thing"),
377
- "cmd_watch must DELEGATE to the watch subsystem (run_watch -> render_event_line) and surface the \
378
- rendered watch line; today it is a CmdResult::none() no-op. got output={:?}",
379
- r.output
380
- );
381
- }
382
-
383
299
  // D7 [WARN] — WAVE-2 Lane B: empty caller binds refuse as caller_pane_missing.
384
300
  #[test]
385
301
  #[serial_test::serial(env)]
@@ -175,58 +175,7 @@ fn inbox_matches_sender_or_recipient_and_excludes_others() {
175
175
  );
176
176
  let _ = std::fs::remove_dir_all(&ws);
177
177
  }
178
- // ── BUG-4 [real bug] — peek must resolve the agent terminal via state `session:window` (golden),
179
- // NOT a stored `pane_id` field. A live worker present in state (with a `window`, on the team's `-L`
180
- // socket) must NOT be fabricated as "agent pane not found". ──────────────────────────────────────
181
- // Golden status/peek.py:35-44: agent = state["agents"][id]; window = agent.get("window", id);
182
- // if not session_name or not _tmux_window_exists(session_name, window): raise "agent terminal is
183
- // not available: <id>"; else `tmux capture-pane -t session:window`. It NEVER reads a stored pane_id.
184
- // Probed live (/tmp/probe_peek.py): a present worker whose window is absent on the socket raises
185
- // `agent terminal is not available: w1`; a missing agent raises `unknown agent id: <id>`. Rust
186
- // cmd_peek (adapters.rs:231 + agent_pane_id:279) keys off agent_state pane_id/pane/tmux_pane_id and
187
- // returns {ok:false,error:"agent pane not found"} when absent — so a NORMAL live worker (window in
188
- // state, no pane_id field) is mis-reported as not found. That is the CP-1 pane-resolution divergence.
189
- //
190
- // Deterministic without real tmux: on a host with no live session, the golden-correct peek resolves
191
- // session:window, finds the window absent on the socket, and yields "agent terminal is not available:
192
- // w1" — NOT "agent pane not found". (The window-on-socket -> real raw-screen capture positive case is
193
- // real-machine; see the #[ignore] dispatch_routes_peek_real_machine.)
194
- #[test]
195
- fn peek_resolves_live_worker_via_session_window_not_pane_id_field() {
196
- let ws = tmp_workspace();
197
- // a live worker: present in state with a `window`, session_name set, but NO stored pane_id field.
198
- // session name is unique so a real tmux session on the dev host can't accidentally satisfy it.
199
- crate::state::persist::save_runtime_state(
200
- &ws,
201
- &json!({
202
- "session_name": "team-peek-red-probe-x9q",
203
- "agents": {"w1": {"status": "running", "provider": "codex", "window": "w1"}}
204
- }),
205
- )
206
- .unwrap();
207
- let args = PeekArgs {
208
- agent: "w1".to_string(),
209
- workspace: ws.clone(),
210
- tail: 20,
211
- head: None,
212
- search: None,
213
- allow_raw_screen: true,
214
- json: true,
215
- };
216
- let text = outcome_text(cmd_peek(&args));
217
- assert!(
218
- !text.contains("agent pane not found"),
219
- "peek keys off a stored pane_id field and fabricates 'agent pane not found' for a live worker \
220
- that has a `window` in state; golden resolves session:window and never reads pane_id. got: {text}"
221
- );
222
- assert!(
223
- text.contains("agent terminal is not available: w1"),
224
- "golden status/peek.py: a worker whose window is not on the socket yields \
225
- 'agent terminal is not available: w1' (window-existence via session:window), NOT a \
226
- pane_id-keyed error. got: {text}"
227
- );
228
- let _ = std::fs::remove_dir_all(&ws);
229
- }
178
+
230
179
  #[test]
231
180
  fn ux_doctor_secret_scan_is_present_and_non_triggering_for_normal_paths() {
232
181
  let ws = tmp_workspace();
@@ -295,44 +244,16 @@ fn ux_doctor_secret_scan_findings_name_the_exact_trigger() {
295
244
  let _ = std::fs::remove_dir_all(&ws);
296
245
  }
297
246
  #[test]
298
- fn ux_wait_ready_does_not_report_ready_true_without_ready_runtime_state() {
299
- let ws = tmp_workspace();
300
- crate::state::persist::save_runtime_state(
301
- &ws,
302
- &json!({
303
- "active_team_key": "wait-ready",
304
- "agents": {"w1": {"status": "starting"}},
305
- "tasks": [{"id": "t1", "assignee": "w1", "status": "pending"}],
306
- "leader_receiver": {"status": "attached"},
307
- }),
308
- )
309
- .unwrap();
310
- let value = json_output(
311
- cmd_wait_ready(&WaitReadyArgs {
312
- workspace: ws.clone(),
313
- timeout: 0.0,
314
- json: true,
315
- team: None,
316
- })
317
- .expect("wait-ready"),
318
- );
319
- assert_eq!(
320
- value["ok"],
321
- json!(false),
322
- "wait-ready must not fake success before workers are ready"
323
- );
324
- assert_eq!(value.pointer("/readiness/ready"), Some(&json!(false)));
325
- assert!(
326
- value["summary"]
327
- .as_str()
328
- .unwrap_or("")
329
- .contains("not ready"),
330
- "wait-ready false state should explain not-ready status, got {value:?}"
331
- );
332
- let _ = std::fs::remove_dir_all(&ws);
247
+ fn runtime_readiness_rejects_starting_workers() {
248
+ let value = crate::cli::diagnose::wait_readiness(&json!({
249
+ "agents": {"w1": {"status": "starting"}},
250
+ "leader_receiver": {"status": "attached"},
251
+ }));
252
+ assert_eq!(value["ready"], json!(false));
253
+ assert_eq!(value["all_spawned"], json!(false));
333
254
  }
334
255
  #[test]
335
- fn wait_ready_fake_quick_start_counts_mcp_config_and_task_prompt_delivery() {
256
+ fn runtime_readiness_counts_mcp_config_and_task_prompt_delivery() {
336
257
  let ws = tmp_workspace();
337
258
  let mcp_config = ws
338
259
  .join(".team")
@@ -342,10 +263,9 @@ fn wait_ready_fake_quick_start_counts_mcp_config_and_task_prompt_delivery() {
342
263
  .join("mcp_config.json");
343
264
  std::fs::create_dir_all(mcp_config.parent().unwrap()).unwrap();
344
265
  std::fs::write(&mcp_config, r#"{"mcpServers":{"team-agent":{}}}"#).unwrap();
345
- crate::state::persist::save_runtime_state(
346
- &ws,
347
- &json!({
266
+ let state = json!({
348
267
  "session_name": "team-fake-ready",
268
+ "messages": {"delivered": 1},
349
269
  "agents": {
350
270
  "fake_impl": {
351
271
  "status": "running",
@@ -359,30 +279,9 @@ fn wait_ready_fake_quick_start_counts_mcp_config_and_task_prompt_delivery() {
359
279
  "status": "pending",
360
280
  }],
361
281
  "leader_receiver": {"status": "attached"},
362
- }),
363
- )
364
- .unwrap();
365
- let store = crate::message_store::MessageStore::open(&ws).unwrap();
366
- store
367
- .create_message(
368
- Some("task_impl"),
369
- "leader",
370
- "fake_impl",
371
- "initial task prompt",
372
- None,
373
- true,
374
- None,
375
- )
376
- .unwrap();
377
- let value = json_output(
378
- cmd_wait_ready(&WaitReadyArgs {
379
- workspace: ws.clone(),
380
- timeout: 0.0,
381
- json: true,
382
- team: None,
383
- })
384
- .expect("wait-ready"),
385
- );
282
+ });
283
+ let readiness = crate::cli::diagnose::wait_readiness(&state);
284
+ let value = json!({"readiness": readiness});
386
285
  assert_eq!(
387
286
  value.pointer("/readiness/mcp_ready"),
388
287
  Some(&json!(true)),
@@ -400,31 +299,7 @@ fn wait_ready_fake_quick_start_counts_mcp_config_and_task_prompt_delivery() {
400
299
  );
401
300
  let _ = std::fs::remove_dir_all(&ws);
402
301
  }
403
- fn valid_result_envelope() -> serde_json::Value {
404
- json!({
405
- "schema_version": "result_envelope_v1",
406
- "task_id": "task_impl",
407
- "agent_id": "fake_impl",
408
- "status": "success",
409
- "summary": "done",
410
- "artifacts": [],
411
- "changes": [],
412
- "tests": [{"command": "cargo test", "status": "passed"}],
413
- "risks": [],
414
- "next_actions": []
415
- })
416
- }
417
- fn seed_uncollected_result(ws: &std::path::Path, result_id: &str) {
418
- let store = crate::message_store::MessageStore::open(ws).unwrap();
419
- let conn = crate::db::schema::open_db(store.db_path()).unwrap();
420
- conn.execute(
421
- "insert into results(
422
- result_id, owner_team_id, task_id, agent_id, envelope, status, created_at
423
- ) values (?1, null, 'task_impl', 'fake_impl', ?2, 'success', '2026-06-02T10:00:00+00:00')",
424
- rusqlite::params![result_id, valid_result_envelope().to_string()],
425
- )
426
- .unwrap();
427
- }
302
+
428
303
  fn json_output(result: CmdResult) -> serde_json::Value {
429
304
  match result.output {
430
305
  CmdOutput::Json(v) => v,