@team-agent/installer 0.5.41 → 0.5.43

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 (152) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +129 -54
  4. package/crates/team-agent/src/cli/diagnose.rs +1 -2
  5. package/crates/team-agent/src/cli/emit.rs +1 -7
  6. package/crates/team-agent/src/cli/helpers.rs +3 -1
  7. package/crates/team-agent/src/cli/leader.rs +2 -1
  8. package/crates/team-agent/src/cli/mod.rs +27 -7
  9. package/crates/team-agent/src/cli/named_address.rs +14 -5
  10. package/crates/team-agent/src/cli/profile.rs +19 -7
  11. package/crates/team-agent/src/cli/send.rs +9 -3
  12. package/crates/team-agent/src/cli/status.rs +8 -30
  13. package/crates/team-agent/src/cli/status_port.rs +1341 -1272
  14. package/crates/team-agent/src/cli/tests/base.rs +738 -660
  15. package/crates/team-agent/src/cli/tests/compile.rs +45 -18
  16. package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
  21. package/crates/team-agent/src/cli/tests/mod.rs +17 -7
  22. package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
  23. package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
  25. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
  26. package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
  27. package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
  28. package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
  29. package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
  30. package/crates/team-agent/src/codex_app_server.rs +2 -5
  31. package/crates/team-agent/src/compiler/tests.rs +139 -33
  32. package/crates/team-agent/src/compiler.rs +55 -22
  33. package/crates/team-agent/src/conpty/backend.rs +23 -33
  34. package/crates/team-agent/src/coordinator/backoff.rs +2 -7
  35. package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
  36. package/crates/team-agent/src/coordinator/health.rs +46 -31
  37. package/crates/team-agent/src/coordinator/mod.rs +3 -3
  38. package/crates/team-agent/src/coordinator/orphan.rs +22 -10
  39. package/crates/team-agent/src/coordinator/steps/abnormal.rs +47 -52
  40. package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
  41. package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
  42. package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
  43. package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
  44. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
  45. package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
  46. package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
  47. package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
  48. package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
  49. package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
  50. package/crates/team-agent/src/coordinator/tick.rs +19 -1
  51. package/crates/team-agent/src/db/message_store.rs +138 -30
  52. package/crates/team-agent/src/db/migration.rs +249 -61
  53. package/crates/team-agent/src/db/schema.rs +303 -82
  54. package/crates/team-agent/src/diagnose/comms.rs +9 -2
  55. package/crates/team-agent/src/diagnose/mod.rs +1 -3
  56. package/crates/team-agent/src/diagnose/orphans.rs +79 -61
  57. package/crates/team-agent/src/event_log.rs +70 -16
  58. package/crates/team-agent/src/layout/manager.rs +15 -4
  59. package/crates/team-agent/src/layout/mod.rs +4 -4
  60. package/crates/team-agent/src/layout/overlay.rs +10 -3
  61. package/crates/team-agent/src/layout/placement.rs +5 -1
  62. package/crates/team-agent/src/layout/recovery.rs +4 -2
  63. package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
  64. package/crates/team-agent/src/layout/sessions.rs +17 -9
  65. package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
  66. package/crates/team-agent/src/layout/worker_env.rs +52 -19
  67. package/crates/team-agent/src/leader/helpers.rs +7 -1
  68. package/crates/team-agent/src/leader/lease.rs +195 -82
  69. package/crates/team-agent/src/leader/owner_bind.rs +55 -22
  70. package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
  71. package/crates/team-agent/src/leader/rediscover.rs +74 -25
  72. package/crates/team-agent/src/leader/start.rs +75 -54
  73. package/crates/team-agent/src/leader/takeover.rs +46 -11
  74. package/crates/team-agent/src/leader/tests/basics.rs +320 -167
  75. package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
  76. package/crates/team-agent/src/leader/tests/identity.rs +428 -356
  77. package/crates/team-agent/src/leader/tests/idle.rs +285 -254
  78. package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
  79. package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
  80. package/crates/team-agent/src/leader/tests/mod.rs +115 -99
  81. package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
  82. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
  83. package/crates/team-agent/src/lib.rs +4 -4
  84. package/crates/team-agent/src/lifecycle/display.rs +7 -3
  85. package/crates/team-agent/src/lifecycle/launch.rs +6 -1
  86. package/crates/team-agent/src/lifecycle/mod.rs +9 -1
  87. package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
  88. package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
  89. package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
  90. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
  91. package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
  92. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
  93. package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
  94. package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
  95. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
  96. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
  97. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
  98. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
  99. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
  100. package/crates/team-agent/src/lifecycle/tests.rs +2 -2
  101. package/crates/team-agent/src/main.rs +4 -4
  102. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
  103. package/crates/team-agent/src/mcp_server/mod.rs +11 -2
  104. package/crates/team-agent/src/mcp_server/types.rs +14 -3
  105. package/crates/team-agent/src/mcp_server/wire.rs +230 -68
  106. package/crates/team-agent/src/messaging/delivery.rs +20 -18
  107. package/crates/team-agent/src/messaging/helpers.rs +25 -4
  108. package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
  109. package/crates/team-agent/src/messaging/mod.rs +2 -3
  110. package/crates/team-agent/src/messaging/selftest.rs +46 -26
  111. package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
  112. package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
  113. package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
  114. package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
  115. package/crates/team-agent/src/messaging/trust.rs +25 -2
  116. package/crates/team-agent/src/messaging/watchers.rs +37 -9
  117. package/crates/team-agent/src/model/enums.rs +65 -16
  118. package/crates/team-agent/src/model/ids.rs +12 -3
  119. package/crates/team-agent/src/model/paths.rs +28 -7
  120. package/crates/team-agent/src/model/permissions.rs +176 -33
  121. package/crates/team-agent/src/model/routing.rs +66 -20
  122. package/crates/team-agent/src/model/spec.rs +365 -69
  123. package/crates/team-agent/src/model/task_graph.rs +36 -9
  124. package/crates/team-agent/src/model/yaml/tests.rs +24 -6
  125. package/crates/team-agent/src/model/yaml.rs +7 -6
  126. package/crates/team-agent/src/packaging/install.rs +23 -9
  127. package/crates/team-agent/src/packaging/migrate.rs +5 -7
  128. package/crates/team-agent/src/packaging/mod.rs +9 -1
  129. package/crates/team-agent/src/packaging/repair.rs +13 -6
  130. package/crates/team-agent/src/packaging/tests.rs +63 -16
  131. package/crates/team-agent/src/packaging/types.rs +22 -7
  132. package/crates/team-agent/src/platform/argv.rs +4 -1
  133. package/crates/team-agent/src/platform/file_lock.rs +22 -8
  134. package/crates/team-agent/src/platform/process.rs +21 -22
  135. package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
  136. package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
  137. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
  138. package/crates/team-agent/src/provider/classify.rs +127 -42
  139. package/crates/team-agent/src/provider/faults.rs +17 -5
  140. package/crates/team-agent/src/provider/helpers.rs +6 -5
  141. package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
  142. package/crates/team-agent/src/state/repository.rs +27 -14
  143. package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
  144. package/crates/team-agent/src/tmux_backend.rs +80 -44
  145. package/crates/team-agent/src/topology.rs +27 -20
  146. package/crates/team-agent/src/transport/test_support.rs +20 -23
  147. package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
  148. package/crates/team-agent/src/transport/tests/mod.rs +178 -187
  149. package/crates/team-agent/src/transport/tests/wire.rs +561 -525
  150. package/crates/team-agent/src/transport.rs +12 -17
  151. package/crates/team-agent/src/transport_factory.rs +29 -14
  152. package/package.json +4 -4
@@ -43,7 +43,9 @@ impl Drop for TeamSocketGuard {
43
43
  if let Some(uid) = current_uid() {
44
44
  let socket = crate::tmux_backend::socket_name_for_workspace(&self.ws);
45
45
  for root in ["/private/tmp", "/tmp"] {
46
- let path = std::path::Path::new(root).join(format!("tmux-{uid}")).join(&socket);
46
+ let path = std::path::Path::new(root)
47
+ .join(format!("tmux-{uid}"))
48
+ .join(&socket);
47
49
  let _ = std::fs::remove_file(path);
48
50
  }
49
51
  }
@@ -55,331 +57,370 @@ fn current_uid() -> Option<String> {
55
57
  if !output.status.success() {
56
58
  return None;
57
59
  }
58
- String::from_utf8(output.stdout).ok().map(|s| s.trim().to_string()).filter(|s| !s.is_empty())
60
+ String::from_utf8(output.stdout)
61
+ .ok()
62
+ .map(|s| s.trim().to_string())
63
+ .filter(|s| !s.is_empty())
59
64
  }
60
65
 
61
- // =========================================================================
62
- // (A) CLI dispatch — run(argv, cwd) parses each subcommand and routes to its (already-tested)
63
- // cmd_*, mapping the exit code. Today run() only handles codex/claude passthrough + help, else
64
- // ExitCode::Error. These prove run DISPATCHES (observable per-command effect + exit). Golden
65
- // cli/parser.py:main. OS-safe handlers only; spawn-bearing routes are #[ignore] real-machine.
66
- // =========================================================================
66
+ // =========================================================================
67
+ // (A) CLI dispatch — run(argv, cwd) parses each subcommand and routes to its (already-tested)
68
+ // cmd_*, mapping the exit code. Today run() only handles codex/claude passthrough + help, else
69
+ // ExitCode::Error. These prove run DISPATCHES (observable per-command effect + exit). Golden
70
+ // cli/parser.py:main. OS-safe handlers only; spawn-bearing routes are #[ignore] real-machine.
71
+ // =========================================================================
67
72
 
68
- #[test]
69
- fn run_dispatches_status_to_handler_returns_ok() {
70
- // run([status --workspace <seeded> --json]) -> cmd_status -> status_port::status (reads state)
71
- // -> ExitCode::Ok. Today: Error (no dispatch). OS-safe (status only reads runtime state).
72
- let ws = seed_status_workspace();
73
- let argv = vec![
74
- "status".to_string(),
75
- "--workspace".to_string(),
76
- ws.to_string_lossy().to_string(),
77
- "--json".to_string(),
78
- ];
79
- assert_eq!(
80
- run(&argv, Path::new(".")),
81
- ExitCode::Ok,
82
- "run must dispatch `status` to cmd_status and return Ok (not today's hardcoded Error)"
83
- );
84
- }
73
+ #[test]
74
+ fn run_dispatches_status_to_handler_returns_ok() {
75
+ // run([status --workspace <seeded> --json]) -> cmd_status -> status_port::status (reads state)
76
+ // -> ExitCode::Ok. Today: Error (no dispatch). OS-safe (status only reads runtime state).
77
+ let ws = seed_status_workspace();
78
+ let argv = vec![
79
+ "status".to_string(),
80
+ "--workspace".to_string(),
81
+ ws.to_string_lossy().to_string(),
82
+ "--json".to_string(),
83
+ ];
84
+ assert_eq!(
85
+ run(&argv, Path::new(".")),
86
+ ExitCode::Ok,
87
+ "run must dispatch `status` to cmd_status and return Ok (not today's hardcoded Error)"
88
+ );
89
+ }
85
90
 
86
- #[test]
87
- fn run_dispatches_send_to_handler_returns_ok() {
88
- // run([send w1 hello --sender leader --workspace <seeded>]) -> cmd_send -> messaging::send_message
89
- // -> ok (w1 is an in-team agent) -> ExitCode::Ok. w1 must be seeded: golden refuses non-team targets.
90
- //
91
- // OLD seed: flat `{"agents": {"w1": ...}}`.
92
- // NEW seed (Bug 1/2 — team-in-team state scope, see
93
- // tests/team_in_team_state_scope_red.rs): send_message projects the raw state
94
- // through the active team_key, so the in-team agent now lives under
95
- // `teams[<key>].agents`. Behavior under test (dispatch + ok exit) unchanged.
96
- let ws = deleg_uniq_dir("run-send");
97
- let _ = crate::message_store::MessageStore::open(&ws).unwrap();
98
- crate::state::persist::save_runtime_state(
99
- &ws,
100
- &serde_json::json!({
101
- "active_team_key": "current",
102
- "teams": {"current": {"agents": {"w1": {"provider": "codex"}}}}
103
- }),
104
- )
105
- .unwrap();
106
- let argv = vec![
107
- "send".to_string(),
108
- "w1".to_string(),
109
- "hello".to_string(),
110
- "--sender".to_string(),
111
- "leader".to_string(),
112
- "--workspace".to_string(),
113
- ws.to_string_lossy().to_string(),
114
- ];
115
- assert_eq!(
116
- run(&argv, Path::new(".")),
117
- ExitCode::Ok,
118
- "run must dispatch `send` to cmd_send and derive ExitCode::Ok from the delivery outcome"
119
- );
120
- }
91
+ #[test]
92
+ fn run_dispatches_send_to_handler_returns_ok() {
93
+ // run([send w1 hello --sender leader --workspace <seeded>]) -> cmd_send -> messaging::send_message
94
+ // -> ok (w1 is an in-team agent) -> ExitCode::Ok. w1 must be seeded: golden refuses non-team targets.
95
+ //
96
+ // OLD seed: flat `{"agents": {"w1": ...}}`.
97
+ // NEW seed (Bug 1/2 — team-in-team state scope, see
98
+ // tests/team_in_team_state_scope_red.rs): send_message projects the raw state
99
+ // through the active team_key, so the in-team agent now lives under
100
+ // `teams[<key>].agents`. Behavior under test (dispatch + ok exit) unchanged.
101
+ let ws = deleg_uniq_dir("run-send");
102
+ let _ = crate::message_store::MessageStore::open(&ws).unwrap();
103
+ crate::state::persist::save_runtime_state(
104
+ &ws,
105
+ &serde_json::json!({
106
+ "active_team_key": "current",
107
+ "teams": {"current": {"agents": {"w1": {"provider": "codex"}}}}
108
+ }),
109
+ )
110
+ .unwrap();
111
+ let argv = vec![
112
+ "send".to_string(),
113
+ "w1".to_string(),
114
+ "hello".to_string(),
115
+ "--sender".to_string(),
116
+ "leader".to_string(),
117
+ "--workspace".to_string(),
118
+ ws.to_string_lossy().to_string(),
119
+ ];
120
+ assert_eq!(
121
+ run(&argv, Path::new(".")),
122
+ ExitCode::Ok,
123
+ "run must dispatch `send` to cmd_send and derive ExitCode::Ok from the delivery outcome"
124
+ );
125
+ }
121
126
 
122
- #[test]
123
- fn run_unknown_subcommand_is_usage() {
124
- // An unknown subcommand routes to argparse-style usage (exit 2), never a handler.
125
- assert_eq!(
126
- run(&["totally-not-a-subcommand".to_string()], Path::new(".")),
127
- ExitCode::Usage,
128
- "an unknown subcommand must map to ExitCode::Usage"
129
- );
130
- }
127
+ #[test]
128
+ fn run_unknown_subcommand_is_usage() {
129
+ // An unknown subcommand routes to argparse-style usage (exit 2), never a handler.
130
+ assert_eq!(
131
+ run(&["totally-not-a-subcommand".to_string()], Path::new(".")),
132
+ ExitCode::Usage,
133
+ "an unknown subcommand must map to ExitCode::Usage"
134
+ );
135
+ }
131
136
 
132
- #[test]
133
- #[ignore = "real-machine: `coordinator --once` dispatches to run_daemon which drives a real \
137
+ #[test]
138
+ #[ignore = "real-machine: `coordinator --once` dispatches to run_daemon which drives a real \
134
139
  Coordinator (TmuxBackend); in-process needs a run_daemon_with_coordinator seam"]
135
- fn run_dispatches_coordinator_once_writes_meta() {
136
- // run([coordinator --workspace <ws> --once]) -> run_daemon(DaemonArgs{once:true}) -> writes the
137
- // coordinator boot metadata + ExitCode::Ok. (real machine — drives a real backend.)
138
- let ws = seed_status_workspace();
139
- let argv = vec![
140
- "coordinator".to_string(),
141
- "--workspace".to_string(),
142
- ws.to_string_lossy().to_string(),
143
- "--once".to_string(),
144
- ];
145
- let exit = run(&argv, Path::new("."));
146
- let wp = crate::coordinator::WorkspacePath::new(ws);
147
- assert!(
148
- crate::coordinator::coordinator_meta_path(&wp).exists(),
149
- "coordinator --once must dispatch to run_daemon and write the coordinator metadata"
150
- );
151
- assert_eq!(exit, ExitCode::Ok);
152
- }
140
+ fn run_dispatches_coordinator_once_writes_meta() {
141
+ // run([coordinator --workspace <ws> --once]) -> run_daemon(DaemonArgs{once:true}) -> writes the
142
+ // coordinator boot metadata + ExitCode::Ok. (real machine — drives a real backend.)
143
+ let ws = seed_status_workspace();
144
+ let argv = vec![
145
+ "coordinator".to_string(),
146
+ "--workspace".to_string(),
147
+ ws.to_string_lossy().to_string(),
148
+ "--once".to_string(),
149
+ ];
150
+ let exit = run(&argv, Path::new("."));
151
+ let wp = crate::coordinator::WorkspacePath::new(ws);
152
+ assert!(
153
+ crate::coordinator::coordinator_meta_path(&wp).exists(),
154
+ "coordinator --once must dispatch to run_daemon and write the coordinator metadata"
155
+ );
156
+ assert_eq!(exit, ExitCode::Ok);
157
+ }
153
158
 
154
- #[test]
155
- #[ignore = "real-machine: quick-start --yes spawns a real team (tmux) + coordinator daemon"]
156
- fn run_dispatches_quick_start_compiles_spec() {
157
- // The full quick-start path spawns workers + the coordinator; on a real machine we assert it
158
- // dispatched to cmd_quick_start (team.spec.yaml compiled under the team dir). With no positive
159
- // caller pane, honest-readiness reports leader_receiver_unbound and exits nonzero.
160
- let _env = EnvGuard::unset(&[
161
- "TMUX",
162
- "TMUX_PANE",
163
- "TEAM_AGENT_ID",
164
- "TEAM_AGENT_TEAM_ID",
165
- "TEAM_AGENT_LEADER_PANE_ID",
166
- "TEAM_AGENT_LEADER_SESSION_UUID",
167
- "TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE",
168
- "TEAM_AGENT_LEADER_PROVIDER",
169
- ]);
170
- let dir = std::env::temp_dir().join(format!("ta-cli-qs-{}", std::process::id()));
171
- std::fs::create_dir_all(dir.join("agents")).unwrap();
172
- std::fs::write(dir.join("TEAM.md"), "---\nname: t\nobjective: o\nprovider: codex\n---\n\nteam.\n").unwrap();
173
- std::fs::write(
159
+ #[test]
160
+ #[ignore = "real-machine: quick-start --yes spawns a real team (tmux) + coordinator daemon"]
161
+ fn run_dispatches_quick_start_compiles_spec() {
162
+ // The full quick-start path spawns workers + the coordinator; on a real machine we assert it
163
+ // dispatched to cmd_quick_start (team.spec.yaml compiled under the team dir). With no positive
164
+ // caller pane, honest-readiness reports leader_receiver_unbound and exits nonzero.
165
+ let _env = EnvGuard::unset(&[
166
+ "TMUX",
167
+ "TMUX_PANE",
168
+ "TEAM_AGENT_ID",
169
+ "TEAM_AGENT_TEAM_ID",
170
+ "TEAM_AGENT_LEADER_PANE_ID",
171
+ "TEAM_AGENT_LEADER_SESSION_UUID",
172
+ "TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE",
173
+ "TEAM_AGENT_LEADER_PROVIDER",
174
+ ]);
175
+ let dir = std::env::temp_dir().join(format!("ta-cli-qs-{}", std::process::id()));
176
+ std::fs::create_dir_all(dir.join("agents")).unwrap();
177
+ std::fs::write(
178
+ dir.join("TEAM.md"),
179
+ "---\nname: t\nobjective: o\nprovider: codex\n---\n\nteam.\n",
180
+ )
181
+ .unwrap();
182
+ std::fs::write(
174
183
  dir.join("agents").join("implementer.md"),
175
184
  "---\nname: implementer\nrole: Impl\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nImpl.\n",
176
185
  )
177
186
  .unwrap();
178
- let argv = vec!["quick-start".to_string(), dir.to_string_lossy().to_string(), "--yes".to_string()];
179
- let exit = run(&argv, Path::new("."));
180
- assert!(dir.join("team.spec.yaml").exists(), "quick-start must compile the spec under the team dir");
181
- assert_eq!(
187
+ let argv = vec![
188
+ "quick-start".to_string(),
189
+ dir.to_string_lossy().to_string(),
190
+ "--yes".to_string(),
191
+ ];
192
+ let exit = run(&argv, Path::new("."));
193
+ assert!(
194
+ dir.join("team.spec.yaml").exists(),
195
+ "quick-start must compile the spec under the team dir"
196
+ );
197
+ assert_eq!(
182
198
  exit,
183
199
  ExitCode::Error,
184
200
  "quick-start must still dispatch and compile, but an unbound leader receiver is an honest-readiness failure, not ExitCode::Ok"
185
201
  );
186
- }
202
+ }
187
203
 
188
- // =========================================================================
189
- // (DELEGATION) cli *_port handlers must DELEGATE TO THE REAL SUBSYSTEMS (crate::lifecycle /
190
- // crate::coordinator / crate::message_store), not the placeholder stubs in cli/mod.rs (which do
191
- // `let _ = (...)` and fabricate canned Values). Each test asserts the REAL subsystem's effect
192
- // (spec compiled, real error text, real coordinator_health, real DB counts) appears in the
193
- // handler's CmdResult/Value — distinguishable from the placeholder's canned output. OS edge mocked;
194
- // real tmux spawn / real daemon = #[ignore]. Golden: lifecycle/launch.rs+restart.rs, compiler.rs,
195
- // coordinator/health.rs, message_store.
196
- // =========================================================================
197
- #[test]
198
- fn cli_quick_start_invokes_real_lifecycle_compiles_spec() {
199
- let team = deleg_team_dir_with_healthy_coordinator();
200
- let ws = crate::model::paths::team_workspace(&team).unwrap();
201
- let _guard = TeamSocketGuard { ws };
202
- let args = QuickStartArgs {
203
- workspace: crate::model::paths::team_workspace(&team).unwrap(),
204
- agents_dir: team.clone(),
205
- name: None,
206
- team_id: None,
207
- yes: true,
208
- no_display: false,
209
- backend: None,
210
- json: true,
211
- };
212
- let _ = cmd_quick_start(&args); // real quick_start compiles the spec before any coordinator/launch step
213
- // E5: spec compiled to .team/runtime/<team_key>/, NOT the user team dir.
214
- let team_key = team.file_name().unwrap().to_string_lossy().to_string();
215
- let workspace = crate::model::paths::team_workspace(&team).unwrap();
216
- let runtime_spec = crate::model::paths::runtime_spec_path(&workspace, &team_key);
217
- assert!(
204
+ // =========================================================================
205
+ // (DELEGATION) cli *_port handlers must DELEGATE TO THE REAL SUBSYSTEMS (crate::lifecycle /
206
+ // crate::coordinator / crate::message_store), not the placeholder stubs in cli/mod.rs (which do
207
+ // `let _ = (...)` and fabricate canned Values). Each test asserts the REAL subsystem's effect
208
+ // (spec compiled, real error text, real coordinator_health, real DB counts) appears in the
209
+ // handler's CmdResult/Value — distinguishable from the placeholder's canned output. OS edge mocked;
210
+ // real tmux spawn / real daemon = #[ignore]. Golden: lifecycle/launch.rs+restart.rs, compiler.rs,
211
+ // coordinator/health.rs, message_store.
212
+ // =========================================================================
213
+ #[test]
214
+ fn cli_quick_start_invokes_real_lifecycle_compiles_spec() {
215
+ let team = deleg_team_dir_with_healthy_coordinator();
216
+ let ws = crate::model::paths::team_workspace(&team).unwrap();
217
+ let _guard = TeamSocketGuard { ws };
218
+ let args = QuickStartArgs {
219
+ workspace: crate::model::paths::team_workspace(&team).unwrap(),
220
+ agents_dir: team.clone(),
221
+ name: None,
222
+ team_id: None,
223
+ yes: true,
224
+ no_display: false,
225
+ backend: None,
226
+ json: true,
227
+ };
228
+ let _ = cmd_quick_start(&args); // real quick_start compiles the spec before any coordinator/launch step
229
+ // E5: spec compiled to .team/runtime/<team_key>/, NOT the user team dir.
230
+ let team_key = team.file_name().unwrap().to_string_lossy().to_string();
231
+ let workspace = crate::model::paths::team_workspace(&team).unwrap();
232
+ let runtime_spec = crate::model::paths::runtime_spec_path(&workspace, &team_key);
233
+ assert!(
218
234
  runtime_spec.exists(),
219
235
  "cmd_quick_start must delegate to crate::lifecycle::quick_start, which compiles team.spec.yaml \
220
236
  under .team/runtime/<team_key>/; the placeholder never writes it"
221
237
  );
222
- assert!(
223
- !team.join("team.spec.yaml").exists(),
224
- "E5: quick_start must NOT write spec into the user team dir"
225
- );
226
- }
238
+ assert!(
239
+ !team.join("team.spec.yaml").exists(),
240
+ "E5: quick_start must NOT write spec into the user team dir"
241
+ );
242
+ }
227
243
 
228
- // 2 [P0] — cmd_quick_start over an INVALID role doc surfaces the REAL compiler error (missing
229
- // `provider`), proving delegation to crate::lifecycle (compile fails before any spawn). The
230
- // placeholder returns a canned {ok:true} with no error -> RED. OS-safe (compile fails early).
231
- #[test]
232
- fn cli_quick_start_invalid_spec_surfaces_real_compile_error() {
233
- let team = deleg_uniq_dir("qsbad");
234
- std::fs::create_dir_all(team.join("agents")).unwrap();
235
- std::fs::write(team.join("TEAM.md"), DELEG_TEAM_MD).unwrap();
236
- std::fs::write(team.join("agents").join("broken.md"), DELEG_INVALID_ROLE).unwrap();
237
- let args = QuickStartArgs {
238
- workspace: crate::model::paths::team_workspace(&team).unwrap(),
239
- agents_dir: team,
240
- name: None,
241
- team_id: None,
242
- yes: false,
243
- no_display: false,
244
- backend: None,
245
- json: true,
246
- };
247
- let text = outcome_text(cmd_quick_start(&args));
248
- assert!(
244
+ // 2 [P0] — cmd_quick_start over an INVALID role doc surfaces the REAL compiler error (missing
245
+ // `provider`), proving delegation to crate::lifecycle (compile fails before any spawn). The
246
+ // placeholder returns a canned {ok:true} with no error -> RED. OS-safe (compile fails early).
247
+ #[test]
248
+ fn cli_quick_start_invalid_spec_surfaces_real_compile_error() {
249
+ let team = deleg_uniq_dir("qsbad");
250
+ std::fs::create_dir_all(team.join("agents")).unwrap();
251
+ std::fs::write(team.join("TEAM.md"), DELEG_TEAM_MD).unwrap();
252
+ std::fs::write(team.join("agents").join("broken.md"), DELEG_INVALID_ROLE).unwrap();
253
+ let args = QuickStartArgs {
254
+ workspace: crate::model::paths::team_workspace(&team).unwrap(),
255
+ agents_dir: team,
256
+ name: None,
257
+ team_id: None,
258
+ yes: false,
259
+ no_display: false,
260
+ backend: None,
261
+ json: true,
262
+ };
263
+ let text = outcome_text(cmd_quick_start(&args));
264
+ assert!(
249
265
  text.contains("provider"),
250
266
  "cmd_quick_start must surface the REAL compiler error for a role doc missing `provider` \
251
267
  (proving delegation to crate::lifecycle, not the placeholder's canned ok:true); got {text}"
252
268
  );
253
- }
269
+ }
254
270
 
255
- // 3 [P1] — cmd_restart on a no-spec workspace surfaces the REAL crate::lifecycle::restart error
256
- // "missing spec for restart" (restart.rs:147). The placeholder returns {ok:true, allow_fresh, team}
257
- // -> RED. OS-safe (restart errors on the missing spec before any spawn).
258
- #[test]
259
- fn cli_restart_missing_spec_surfaces_real_teamselect() {
260
- let ws = deleg_uniq_dir("restart"); // no team.spec.yaml
261
- let args = RestartArgs {
262
- workspace: ws,
263
- team: None,
264
- allow_fresh: false,
265
- session_converge_deadline_ms: None,
266
- json: true,
267
- };
268
- let text = outcome_text(cmd_restart(&args));
269
- // RED-2-STILL: entry gate now resolves via canonical_run_workspace; for a no-spec workspace
270
- // the "no spec" surfacing may come from either the entry gate ("missing spec for restart")
271
- // or, when a stray ancestor .team lets it past, the resolve gate ("active team spec not
272
- // found"). Both are the REAL crate::lifecycle error (not a placeholder ok:true).
273
- assert!(
274
- text.contains("missing spec for restart") || text.contains("active team spec not found"),
275
- "cmd_restart must surface the REAL crate::lifecycle::restart missing-spec error \
271
+ // 3 [P1] — cmd_restart on a no-spec workspace surfaces the REAL crate::lifecycle::restart error
272
+ // "missing spec for restart" (restart.rs:147). The placeholder returns {ok:true, allow_fresh, team}
273
+ // -> RED. OS-safe (restart errors on the missing spec before any spawn).
274
+ #[test]
275
+ fn cli_restart_missing_spec_surfaces_real_teamselect() {
276
+ let ws = deleg_uniq_dir("restart"); // no team.spec.yaml
277
+ let args = RestartArgs {
278
+ workspace: ws,
279
+ team: None,
280
+ allow_fresh: false,
281
+ session_converge_deadline_ms: None,
282
+ json: true,
283
+ };
284
+ let text = outcome_text(cmd_restart(&args));
285
+ // RED-2-STILL: entry gate now resolves via canonical_run_workspace; for a no-spec workspace
286
+ // the "no spec" surfacing may come from either the entry gate ("missing spec for restart")
287
+ // or, when a stray ancestor .team lets it past, the resolve gate ("active team spec not
288
+ // found"). Both are the REAL crate::lifecycle error (not a placeholder ok:true).
289
+ assert!(
290
+ text.contains("missing spec for restart") || text.contains("active team spec not found"),
291
+ "cmd_restart must surface the REAL crate::lifecycle::restart missing-spec error \
276
292
  (not the placeholder's canned ok:true); got {text}"
277
- );
278
- }
293
+ );
294
+ }
279
295
 
280
- // 4 [P1] — cmd_add_agent with a DUPLICATE runtime agent id surfaces the REAL
281
- // crate::lifecycle::add_agent "agent id already exists" error. E25: role docs/spec files are
282
- // inputs, not membership; duplicate membership is runtime-state based.
283
- #[test]
284
- fn cli_add_agent_duplicate_id_surfaces_real_error() {
285
- let team = deleg_uniq_dir("addagent");
286
- std::fs::create_dir_all(team.join("agents")).unwrap();
287
- std::fs::write(team.join("TEAM.md"), DELEG_TEAM_MD).unwrap();
288
- std::fs::write(team.join("agents").join("implementer.md"), DELEG_VALID_ROLE).unwrap(); // 'implementer' already exists
289
- crate::state::persist::save_runtime_state(
290
- &team,
291
- &json!({
292
- "session_name": "team-deleg-addagent",
293
- "agents": {
294
- "implementer": {"status": "running", "provider": "codex", "window": "implementer"}
295
- }
296
- }),
297
- )
298
- .unwrap();
299
- let dup_role = team.join("dup-role.md");
300
- std::fs::write(&dup_role, DELEG_VALID_ROLE).unwrap(); // role file must exist
301
- let args = AddAgentArgs {
302
- agent: "implementer".to_string(),
303
- workspace: team,
304
- team: None,
305
- role_file: dup_role.to_string_lossy().to_string(),
306
- no_display: false,
307
- json: true,
308
- };
309
- let text = outcome_text(cmd_add_agent(&args));
310
- assert!(
296
+ // 4 [P1] — cmd_add_agent with a DUPLICATE runtime agent id surfaces the REAL
297
+ // crate::lifecycle::add_agent "agent id already exists" error. E25: role docs/spec files are
298
+ // inputs, not membership; duplicate membership is runtime-state based.
299
+ #[test]
300
+ fn cli_add_agent_duplicate_id_surfaces_real_error() {
301
+ let team = deleg_uniq_dir("addagent");
302
+ std::fs::create_dir_all(team.join("agents")).unwrap();
303
+ std::fs::write(team.join("TEAM.md"), DELEG_TEAM_MD).unwrap();
304
+ std::fs::write(team.join("agents").join("implementer.md"), DELEG_VALID_ROLE).unwrap(); // 'implementer' already exists
305
+ crate::state::persist::save_runtime_state(
306
+ &team,
307
+ &json!({
308
+ "session_name": "team-deleg-addagent",
309
+ "agents": {
310
+ "implementer": {"status": "running", "provider": "codex", "window": "implementer"}
311
+ }
312
+ }),
313
+ )
314
+ .unwrap();
315
+ let dup_role = team.join("dup-role.md");
316
+ std::fs::write(&dup_role, DELEG_VALID_ROLE).unwrap(); // role file must exist
317
+ let args = AddAgentArgs {
318
+ agent: "implementer".to_string(),
319
+ workspace: team,
320
+ team: None,
321
+ role_file: dup_role.to_string_lossy().to_string(),
322
+ no_display: false,
323
+ json: true,
324
+ };
325
+ let text = outcome_text(cmd_add_agent(&args));
326
+ assert!(
311
327
  text.contains("already exists"),
312
328
  "cmd_add_agent must surface the REAL crate::lifecycle::add_agent 'agent id already exists' error \
313
329
  (not the placeholder's canned ok:true); got {text}"
314
330
  );
315
- }
331
+ }
316
332
 
317
- // 5 [P1] — cmd_status --json reflects the REAL coordinator_health + real DB message/result counts.
318
- // The placeholder status_port::status hardcodes coordinator={status:stopped, schema_ok:false} and
319
- // messages/results={count:0}. Seed: state.json + a real team.db (2 messages + 1 result) + a healthy
320
- // coordinator (this pid + metadata) -> the real path must surface running-coordinator + non-zero
321
- // counts. OS-safe (all reads/seeds, no spawn).
322
- #[test]
323
- fn cli_status_reflects_real_coordinator_health_and_db_counts() {
324
- let ws = seed_status_workspace(); // writes .team/runtime/state.json
325
- let store = crate::message_store::MessageStore::open(&ws).unwrap();
326
- let _ = store.create_message(Some("t1"), "leader", "a1", "hi", None, true, None).unwrap();
327
- let _ = store.create_message(Some("t1"), "leader", "a1", "again", None, true, None).unwrap();
328
- let conn = crate::db::schema::open_db(store.db_path()).unwrap();
329
- conn.execute(
333
+ // 5 [P1] — cmd_status --json reflects the REAL coordinator_health + real DB message/result counts.
334
+ // The placeholder status_port::status hardcodes coordinator={status:stopped, schema_ok:false} and
335
+ // messages/results={count:0}. Seed: state.json + a real team.db (2 messages + 1 result) + a healthy
336
+ // coordinator (this pid + metadata) -> the real path must surface running-coordinator + non-zero
337
+ // counts. OS-safe (all reads/seeds, no spawn).
338
+ #[test]
339
+ fn cli_status_reflects_real_coordinator_health_and_db_counts() {
340
+ let ws = seed_status_workspace(); // writes .team/runtime/state.json
341
+ let store = crate::message_store::MessageStore::open(&ws).unwrap();
342
+ let _ = store
343
+ .create_message(Some("t1"), "leader", "a1", "hi", None, true, None)
344
+ .unwrap();
345
+ let _ = store
346
+ .create_message(Some("t1"), "leader", "a1", "again", None, true, None)
347
+ .unwrap();
348
+ let conn = crate::db::schema::open_db(store.db_path()).unwrap();
349
+ conn.execute(
330
350
  "insert into results(result_id, owner_team_id, task_id, agent_id, envelope, status, created_at) \
331
351
  values ('r1', null, 't1', 'a1', '{}', 'success', '2026-01-01T00:00:00Z')",
332
352
  [],
333
353
  )
334
354
  .unwrap();
335
- drop(store);
336
- // a HEALTHY coordinator at the status workspace (this process's pid + matching metadata).
337
- let wp = crate::coordinator::WorkspacePath::new(ws.clone());
338
- let me = crate::coordinator::Pid::new(std::process::id());
339
- crate::coordinator::write_coordinator_metadata(&wp, me, crate::coordinator::MetadataSource::Boot).unwrap();
340
- std::fs::write(crate::coordinator::coordinator_pid_path(&wp), me.to_string()).unwrap();
355
+ drop(store);
356
+ // a HEALTHY coordinator at the status workspace (this process's pid + matching metadata).
357
+ let wp = crate::coordinator::WorkspacePath::new(ws.clone());
358
+ let me = crate::coordinator::Pid::new(std::process::id());
359
+ crate::coordinator::write_coordinator_metadata(
360
+ &wp,
361
+ me,
362
+ crate::coordinator::MetadataSource::Boot,
363
+ )
364
+ .unwrap();
365
+ std::fs::write(
366
+ crate::coordinator::coordinator_pid_path(&wp),
367
+ me.to_string(),
368
+ )
369
+ .unwrap();
341
370
 
342
- let args = StatusArgs { agent: None, workspace: ws, detail: true, summary: false, json: true, team: None };
343
- let r = cmd_status(&args).expect("cmd_status");
344
- let value = match r.output {
345
- CmdOutput::Json(v) => v,
346
- other => panic!("status --json must yield a Json CmdOutput; got {other:?}"),
347
- };
348
- assert_ne!(
349
- value["coordinator"],
350
- json!({"status": "stopped", "schema_ok": false}),
351
- "status `coordinator` must reflect the REAL coordinator_health (seeded running), not the \
371
+ let args = StatusArgs {
372
+ agent: None,
373
+ workspace: ws,
374
+ detail: true,
375
+ summary: false,
376
+ json: true,
377
+ team: None,
378
+ };
379
+ let r = cmd_status(&args).expect("cmd_status");
380
+ let value = match r.output {
381
+ CmdOutput::Json(v) => v,
382
+ other => panic!("status --json must yield a Json CmdOutput; got {other:?}"),
383
+ };
384
+ assert_ne!(
385
+ value["coordinator"],
386
+ json!({"status": "stopped", "schema_ok": false}),
387
+ "status `coordinator` must reflect the REAL coordinator_health (seeded running), not the \
352
388
  placeholder's hardcoded {{status:stopped, schema_ok:false}}; got {}",
353
- value["coordinator"]
354
- );
355
- assert_ne!(
389
+ value["coordinator"]
390
+ );
391
+ assert_ne!(
356
392
  value["messages"],
357
393
  json!({"count": 0}),
358
394
  "status `messages` must reflect the REAL DB rows (2 seeded), not the placeholder's {{count:0}}; got {}",
359
395
  value["messages"]
360
396
  );
361
- assert_ne!(
397
+ assert_ne!(
362
398
  value["results"],
363
399
  json!({"count": 0}),
364
400
  "status `results` must reflect the REAL DB result row, not the placeholder's {{count:0}}; got {}",
365
401
  value["results"]
366
402
  );
367
- }
403
+ }
368
404
 
369
- // 6 [P1] — cmd_shutdown delegates to the REAL crate::coordinator::stop_coordinator (+ tmux
370
- // kill-session). The placeholder lifecycle_port::shutdown returns only {ok:true, keep_logs, team}.
371
- // #[ignore] real-machine: the real shutdown spawns `tmux kill-session` (and SIGTERMs the coordinator);
372
- // on a no-coordinator workspace the real stop_coordinator outcome (stopped/missing) must surface.
373
- #[test]
374
- #[ignore = "real-machine: cmd_shutdown runs the real tmux kill-session + SIGTERMs the coordinator daemon"]
375
- fn cli_shutdown_invokes_real_stop_coordinator() {
376
- let ws = seed_status_workspace();
377
- let args = ShutdownArgs { workspace: ws, team: None, keep_logs: true, json: true };
378
- let text = outcome_text(cmd_shutdown(&args));
379
- let lower = text.to_lowercase();
380
- assert!(
405
+ // 6 [P1] — cmd_shutdown delegates to the REAL crate::coordinator::stop_coordinator (+ tmux
406
+ // kill-session). The placeholder lifecycle_port::shutdown returns only {ok:true, keep_logs, team}.
407
+ // #[ignore] real-machine: the real shutdown spawns `tmux kill-session` (and SIGTERMs the coordinator);
408
+ // on a no-coordinator workspace the real stop_coordinator outcome (stopped/missing) must surface.
409
+ #[test]
410
+ #[ignore = "real-machine: cmd_shutdown runs the real tmux kill-session + SIGTERMs the coordinator daemon"]
411
+ fn cli_shutdown_invokes_real_stop_coordinator() {
412
+ let ws = seed_status_workspace();
413
+ let args = ShutdownArgs {
414
+ workspace: ws,
415
+ team: None,
416
+ keep_logs: true,
417
+ json: true,
418
+ };
419
+ let text = outcome_text(cmd_shutdown(&args));
420
+ let lower = text.to_lowercase();
421
+ assert!(
381
422
  lower.contains("coordinator") || lower.contains("stopped") || lower.contains("missing"),
382
423
  "cmd_shutdown must delegate to the real coordinator::stop_coordinator and surface its outcome \
383
424
  (no coordinator -> stopped/missing); the placeholder returns only {{ok,keep_logs,team}}; got {text}"
384
425
  );
385
- }
426
+ }