@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +129 -54
- package/crates/team-agent/src/cli/diagnose.rs +1 -2
- package/crates/team-agent/src/cli/emit.rs +1 -7
- package/crates/team-agent/src/cli/helpers.rs +3 -1
- package/crates/team-agent/src/cli/leader.rs +2 -1
- package/crates/team-agent/src/cli/mod.rs +27 -7
- package/crates/team-agent/src/cli/named_address.rs +14 -5
- package/crates/team-agent/src/cli/profile.rs +19 -7
- package/crates/team-agent/src/cli/send.rs +9 -3
- package/crates/team-agent/src/cli/status.rs +8 -30
- package/crates/team-agent/src/cli/status_port.rs +1341 -1272
- package/crates/team-agent/src/cli/tests/base.rs +738 -660
- package/crates/team-agent/src/cli/tests/compile.rs +45 -18
- package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
- package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
- package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
- package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
- package/crates/team-agent/src/cli/tests/mod.rs +17 -7
- package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
- package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
- package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
- package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
- package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
- package/crates/team-agent/src/codex_app_server.rs +2 -5
- package/crates/team-agent/src/compiler/tests.rs +139 -33
- package/crates/team-agent/src/compiler.rs +55 -22
- package/crates/team-agent/src/conpty/backend.rs +23 -33
- package/crates/team-agent/src/coordinator/backoff.rs +2 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
- package/crates/team-agent/src/coordinator/health.rs +46 -31
- package/crates/team-agent/src/coordinator/mod.rs +3 -3
- package/crates/team-agent/src/coordinator/orphan.rs +22 -10
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +47 -52
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
- package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
- package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
- package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
- package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
- package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
- package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
- package/crates/team-agent/src/coordinator/tick.rs +19 -1
- package/crates/team-agent/src/db/message_store.rs +138 -30
- package/crates/team-agent/src/db/migration.rs +249 -61
- package/crates/team-agent/src/db/schema.rs +303 -82
- package/crates/team-agent/src/diagnose/comms.rs +9 -2
- package/crates/team-agent/src/diagnose/mod.rs +1 -3
- package/crates/team-agent/src/diagnose/orphans.rs +79 -61
- package/crates/team-agent/src/event_log.rs +70 -16
- package/crates/team-agent/src/layout/manager.rs +15 -4
- package/crates/team-agent/src/layout/mod.rs +4 -4
- package/crates/team-agent/src/layout/overlay.rs +10 -3
- package/crates/team-agent/src/layout/placement.rs +5 -1
- package/crates/team-agent/src/layout/recovery.rs +4 -2
- package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
- package/crates/team-agent/src/layout/sessions.rs +17 -9
- package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
- package/crates/team-agent/src/layout/worker_env.rs +52 -19
- package/crates/team-agent/src/leader/helpers.rs +7 -1
- package/crates/team-agent/src/leader/lease.rs +195 -82
- package/crates/team-agent/src/leader/owner_bind.rs +55 -22
- package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
- package/crates/team-agent/src/leader/rediscover.rs +74 -25
- package/crates/team-agent/src/leader/start.rs +75 -54
- package/crates/team-agent/src/leader/takeover.rs +46 -11
- package/crates/team-agent/src/leader/tests/basics.rs +320 -167
- package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
- package/crates/team-agent/src/leader/tests/identity.rs +428 -356
- package/crates/team-agent/src/leader/tests/idle.rs +285 -254
- package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
- package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
- package/crates/team-agent/src/leader/tests/mod.rs +115 -99
- package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
- package/crates/team-agent/src/lib.rs +4 -4
- package/crates/team-agent/src/lifecycle/display.rs +7 -3
- package/crates/team-agent/src/lifecycle/launch.rs +6 -1
- package/crates/team-agent/src/lifecycle/mod.rs +9 -1
- package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
- package/crates/team-agent/src/lifecycle/tests.rs +2 -2
- package/crates/team-agent/src/main.rs +4 -4
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
- package/crates/team-agent/src/mcp_server/mod.rs +11 -2
- package/crates/team-agent/src/mcp_server/types.rs +14 -3
- package/crates/team-agent/src/mcp_server/wire.rs +230 -68
- package/crates/team-agent/src/messaging/delivery.rs +20 -18
- package/crates/team-agent/src/messaging/helpers.rs +25 -4
- package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
- package/crates/team-agent/src/messaging/mod.rs +2 -3
- package/crates/team-agent/src/messaging/selftest.rs +46 -26
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
- package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
- package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
- package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
- package/crates/team-agent/src/messaging/trust.rs +25 -2
- package/crates/team-agent/src/messaging/watchers.rs +37 -9
- package/crates/team-agent/src/model/enums.rs +65 -16
- package/crates/team-agent/src/model/ids.rs +12 -3
- package/crates/team-agent/src/model/paths.rs +28 -7
- package/crates/team-agent/src/model/permissions.rs +176 -33
- package/crates/team-agent/src/model/routing.rs +66 -20
- package/crates/team-agent/src/model/spec.rs +365 -69
- package/crates/team-agent/src/model/task_graph.rs +36 -9
- package/crates/team-agent/src/model/yaml/tests.rs +24 -6
- package/crates/team-agent/src/model/yaml.rs +7 -6
- package/crates/team-agent/src/packaging/install.rs +23 -9
- package/crates/team-agent/src/packaging/migrate.rs +5 -7
- package/crates/team-agent/src/packaging/mod.rs +9 -1
- package/crates/team-agent/src/packaging/repair.rs +13 -6
- package/crates/team-agent/src/packaging/tests.rs +63 -16
- package/crates/team-agent/src/packaging/types.rs +22 -7
- package/crates/team-agent/src/platform/argv.rs +4 -1
- package/crates/team-agent/src/platform/file_lock.rs +22 -8
- package/crates/team-agent/src/platform/process.rs +21 -22
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
- package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
- package/crates/team-agent/src/provider/classify.rs +127 -42
- package/crates/team-agent/src/provider/faults.rs +17 -5
- package/crates/team-agent/src/provider/helpers.rs +6 -5
- package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
- package/crates/team-agent/src/state/repository.rs +27 -14
- package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
- package/crates/team-agent/src/tmux_backend.rs +80 -44
- package/crates/team-agent/src/topology.rs +27 -20
- package/crates/team-agent/src/transport/test_support.rs +20 -23
- package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
- package/crates/team-agent/src/transport/tests/mod.rs +178 -187
- package/crates/team-agent/src/transport/tests/wire.rs +561 -525
- package/crates/team-agent/src/transport.rs +12 -17
- package/crates/team-agent/src/transport_factory.rs +29 -14
- 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)
|
|
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)
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
+
}
|