@team-agent/installer 0.5.111 → 0.5.112

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 (38) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +1 -743
  4. package/crates/team-agent/src/cli/diagnose.rs +2 -424
  5. package/crates/team-agent/src/cli/emit.rs +31 -275
  6. package/crates/team-agent/src/cli/mod.rs +2 -9
  7. package/crates/team-agent/src/cli/spec.rs +0 -3
  8. package/crates/team-agent/src/cli/tests/leader_watch.rs +2 -86
  9. package/crates/team-agent/src/cli/tests/main_preserved.rs +15 -140
  10. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +1 -258
  11. package/crates/team-agent/src/cli/tests/mod.rs +0 -2
  12. package/crates/team-agent/src/cli/types.rs +4 -108
  13. package/crates/team-agent/src/coordinator/health.rs +3 -283
  14. package/crates/team-agent/src/coordinator/mod.rs +0 -5
  15. package/crates/team-agent/src/coordinator/tests/basics.rs +0 -9
  16. package/crates/team-agent/src/coordinator/tests/mod.rs +0 -1
  17. package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -17
  18. package/crates/team-agent/src/coordinator/types.rs +1 -21
  19. package/crates/team-agent/src/db/message_store.rs +0 -42
  20. package/crates/team-agent/src/db/migration.rs +49 -9
  21. package/crates/team-agent/src/db/schema.rs +27 -20
  22. package/crates/team-agent/src/leader/owner_bind.rs +0 -24
  23. package/crates/team-agent/src/leader/tests/byte_findings.rs +0 -20
  24. package/crates/team-agent/src/leader/tests/identity.rs +0 -32
  25. package/crates/team-agent/src/leader/tests/lease_claim.rs +0 -55
  26. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -51
  27. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +23 -23
  28. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +29 -55
  29. package/crates/team-agent/src/messaging/mod.rs +0 -2
  30. package/crates/team-agent/src/messaging/results.rs +0 -58
  31. package/crates/team-agent/src/messaging/tests/runtime.rs +0 -46
  32. package/crates/team-agent/src/state/repository.rs +1 -5
  33. package/package.json +4 -4
  34. package/templates/team_state.md +1 -1
  35. package/crates/team-agent/src/cli/tests/peer_allow.rs +0 -164
  36. package/crates/team-agent/src/cli/tests/verb_validate.rs +0 -195
  37. package/crates/team-agent/src/coordinator/tests/watch.rs +0 -223
  38. package/crates/team-agent/src/messaging/peers.rs +0 -34
@@ -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,
@@ -144,155 +144,12 @@ fn seed_leader_registry_entry(ws: &std::path::Path) {
144
144
  );
145
145
  }
146
146
 
147
- // =========================================================================
148
- // WAVE-2 NON-SUB CHECKPOINT — missing CLI subcommands (ABSENT from cli/emit.rs dispatch).
149
- // emit.rs `dispatch` has NO arm for sessions, peek, collect, e2e, diagnose,
150
- // preflight, wait-ready -> they fall to `_ => Ok(ExitCode::Error)`. These REDs
151
- // assert the dispatch ROUTES each subcommand: `run([sub,...]) == ExitCode::Ok` for a golden
152
- // EXIT-0 scenario (today unrouted -> ExitCode::Error -> RED; green once the porter adds the
153
- // dispatch arm + handler). Golden exit codes + JSON shapes probed via `python3 -m team_agent <sub>`.
154
- //
155
- // OBSERVABILITY NOTE: `run()` exposes only ExitCode (Ok=0/Error=1); it prints via println!, which
156
- // libtest intercepts (thread-local capture), so an fd-level stdout byte-capture is unreliable
157
- // under `cargo test`. The exact golden --json byte-shape is therefore LOCKED in each doc-comment
158
- // as the porter's parity obligation; the in-process assertion is the routing (exit code). A
159
- // follow-up can byte-lock output once each handler exists as a callable `cmd_*`/`*_port` symbol.
160
- //
161
- // Only golden-EXIT-0, tmux-SAFE scenarios make a clean RED (an exit-1 scenario's Error is
162
- // indistinguishable from the unknown-subcommand Error -> false-green, forbidden). preflight/
163
- // wait-ready/e2e/peek cannot reach golden-exit-0 on CI without real tmux/providers/a live team,
164
- // so they are #[ignore] real-effect seams (documented shape), NOT false-green exit-1 asserts.
165
- // =========================================================================
147
+ // Retained doctor dispatch and shared status-readiness contracts.
166
148
 
167
149
  fn cli_argv(items: &[&str]) -> Vec<String> {
168
150
  items.iter().map(|s| (*s).to_string()).collect()
169
151
  }
170
152
 
171
- /// The exact golden fake spec (cli/e2e.py `_fake_spec`, dumped via simple_yaml) with the literal
172
- /// `/WS` placeholder substituted to the real workspace — the minimal VALID team.spec.yaml that
173
- /// `collect` accepts. (load_spec rejects partial specs: requires
174
- /// communication/context/leader/routing/runtime/tasks.)
175
- const FAKE_SPEC_YAML: &str = r#"version: 1
176
- team:
177
- name: "fake-e2e"
178
- mode: "supervisor_worker"
179
- objective: "Exercise fake provider orchestration."
180
- workspace: "/WS"
181
- leader:
182
- id: "leader"
183
- role: "leader"
184
- provider: "fake"
185
- model: null
186
- tools:
187
- - "fs_read"
188
- - "fs_list"
189
- - "mcp_team"
190
- context_policy:
191
- keep_user_thread: true
192
- receive_worker_outputs: "structured_only"
193
- max_worker_result_tokens: 2000
194
- agents:
195
- - id: "fake_impl"
196
- role: "implementation_engineer"
197
- provider: "fake"
198
- model: null
199
- working_directory: "/WS"
200
- system_prompt:
201
- inline: "Handle fake implementation tasks."
202
- file: null
203
- tools:
204
- - "fs_read"
205
- - "fs_write"
206
- - "fs_list"
207
- - "execute_bash"
208
- - "git_diff"
209
- - "mcp_team"
210
- - "provider_builtin"
211
- permission_mode: "restricted"
212
- preferred_for:
213
- - "implementation"
214
- avoid_for: []
215
- output_contract:
216
- format: "result_envelope_v1"
217
- required_fields:
218
- - "task_id"
219
- - "status"
220
- - "summary"
221
- - "artifacts"
222
- routing:
223
- default_assignee: "leader"
224
- rules:
225
- - id: "implementation-to-fake"
226
- match:
227
- type:
228
- - "implementation"
229
- assign_to: "fake_impl"
230
- priority: 10
231
- communication:
232
- protocol: "mcp_inbox"
233
- topology: "leader_centered"
234
- worker_to_worker: true
235
- ack_timeout_sec: 2
236
- result_format: "result_envelope_v1"
237
- message_store:
238
- sqlite: ".team/runtime/team.db"
239
- mirror_files: ".team/messages"
240
- runtime:
241
- backend: "tmux"
242
- session_name: "team-agent-fake-e2e"
243
- auto_launch: true
244
- require_user_approval_before_launch: false
245
- max_active_agents: 1
246
- startup_order:
247
- - "fake_impl"
248
- context:
249
- state_file: "team_state.md"
250
- artifact_dir: ".team/artifacts"
251
- log_dir: ".team/logs"
252
- summarization:
253
- worker_full_logs: "retain_outside_leader_context"
254
- state_update: "after_each_result"
255
- tasks:
256
- - id: "task_impl"
257
- title: "Fake implementation"
258
- type: "implementation"
259
- assignee: null
260
- deps: []
261
- acceptance:
262
- - "fake result collected"
263
- status: "pending"
264
- requires_tools:
265
- - "fs_write"
266
- - "execute_bash"
267
- files:
268
- - "src/example.py"
269
- risk: "low"
270
- "#;
271
-
272
- fn seed_team_spec(ws: &std::path::Path) {
273
- let spec = FAKE_SPEC_YAML.replace("/WS", &ws.to_string_lossy());
274
- std::fs::write(ws.join("team.spec.yaml"), spec).unwrap();
275
- }
276
-
277
- // ── sessions ── golden cli/parser.py:230 `cmd_sessions` -> runtime.sessions(ws). EXIT 0.
278
- // `team-agent sessions --workspace <ws> --json` on an empty ws ->
279
- // {"ok":true,"sessions":[],"workspace":"<ws>"} (--json sort_keys). RED: unrouted -> Error.
280
- #[test]
281
- fn dispatch_routes_sessions_subcommand() {
282
- let ws = tmp_workspace();
283
- let code = run(
284
- &cli_argv(&["sessions", "--workspace", &ws.to_string_lossy(), "--json"]),
285
- &ws,
286
- );
287
- assert_eq!(
288
- code,
289
- ExitCode::Ok,
290
- "`sessions` must ROUTE to cmd_sessions (golden parser.py:230, exit 0 {{ok,sessions,workspace}}); \
291
- today it falls to the unknown-subcommand arm (emit.rs:77) -> Error"
292
- );
293
- let _ = std::fs::remove_dir_all(&ws);
294
- }
295
-
296
153
  // ── diagnose ── attached is host-registry authority, not state.json self-sign.
297
154
  // Seed leader_receiver=attached + a ~/.team-agent/leaders/ row for this workspace
298
155
  // + NO session_name + NO agents -> issues=[] -> EXIT 0.
@@ -389,120 +246,6 @@ fn dispatch_routes_diagnose_self_signed_attached_without_registry_is_not_ok() {
389
246
  let _ = std::fs::remove_dir_all(&ws);
390
247
  }
391
248
 
392
- // ── preflight (#[ignore] real-machine) ── golden parser.py:160 `cmd_preflight` -> runtime.preflight
393
- // (Path(args.team)). Validates a team role-doc dir: checks compile(TEAM.md)/tmux/ghostty/rust_core/
394
- // profile_dir; golden -> {"blockers":[...],"checks":[...],"details_log":...,"next_actions":[...],
395
- // "ok":bool,"summary":...}. Reaches ok:true (exit 0) ONLY with a valid TEAM.md + ghostty installed,
396
- // which CI cannot supply (ghostty absent -> blocker) -> not a clean in-process exit-0 RED.
397
- #[test]
398
- #[ignore = "real-machine: `preflight --team <dir>` needs a valid TEAM.md role-doc dir + ghostty to \
399
- reach ok:true; on CI it golden-exits 1 (compile/ghostty blockers) which is \
400
- indistinguishable from the unknown-subcommand Error (false-green). Routes to cmd_preflight \
401
- (parser.py:160); shape {blockers,checks,details_log,next_actions,ok,summary}"]
402
- fn dispatch_routes_preflight_real_machine() {
403
- let ws = tmp_workspace();
404
- let code = run(
405
- &cli_argv(&["preflight", "--team", &ws.to_string_lossy(), "--json"]),
406
- &ws,
407
- );
408
- assert_eq!(
409
- code,
410
- ExitCode::Ok,
411
- "preflight must ROUTE + exit 0 on a valid team dir"
412
- );
413
- let _ = std::fs::remove_dir_all(&ws);
414
- }
415
-
416
- // ── wait-ready (#[ignore] real-machine) ── golden parser.py:171 `cmd_wait_ready` -> runtime.wait_ready
417
- // (ws, timeout). Polls worker readiness; golden -> {"details_log":...,"next_actions":[...],"ok":bool,
418
- // "readiness":{cli_prompt_ready,mcp_ready,process_started,task_prompt_delivered},"summary":...}.
419
- // ok:true (exit 0) needs a LIVE ready team; CI has none -> golden-exits 1 (false-green vs unknown).
420
- #[test]
421
- #[ignore = "real-machine: `wait-ready` polls a LIVE team's readiness; with no workers golden-exits 1 \
422
- (false-green vs unknown-subcommand Error). Routes to cmd_wait_ready (parser.py:171); shape \
423
- {details_log,next_actions,ok,readiness{cli_prompt_ready,mcp_ready,process_started,task_prompt_delivered},summary}"]
424
- fn dispatch_routes_wait_ready_real_machine() {
425
- let ws = tmp_workspace();
426
- let code = run(
427
- &cli_argv(&[
428
- "wait-ready",
429
- "--workspace",
430
- &ws.to_string_lossy(),
431
- "--timeout",
432
- "1",
433
- "--json",
434
- ]),
435
- &ws,
436
- );
437
- assert_eq!(
438
- code,
439
- ExitCode::Ok,
440
- "wait-ready must ROUTE + exit 0 once the team is ready"
441
- );
442
- let _ = std::fs::remove_dir_all(&ws);
443
- }
444
-
445
- // ── e2e --providers fake (#[ignore] real-machine) ── golden parser.py:449 `cmd_e2e` (cli/e2e.py:12).
446
- // `--providers fake` runs a REAL end-to-end: runtime.launch(spec, auto_approve=True) (spawns a tmux
447
- // team) -> send_message -> sleep -> collect -> shutdown. Result envelope (e2e.py:16,48-56):
448
- // {"workspace":str,"providers":{"fake":{"ok":bool,"launch":{...},"send":{...},"collect":{...},
449
- // "shutdown":{...}}},"ok":bool}. Real tmux spawn -> #[ignore] (NOT runnable in-process).
450
- #[test]
451
- #[ignore = "real-machine: `e2e --providers fake` calls runtime.launch -> spawns a REAL tmux team \
452
- (send/collect/shutdown). Routes to cmd_e2e (parser.py:449 / e2e.py:12); envelope \
453
- {workspace,providers:{fake:{ok,launch,send,collect,shutdown}},ok}"]
454
- fn dispatch_routes_e2e_fake_real_machine() {
455
- let ws = tmp_workspace();
456
- let code = run(
457
- &cli_argv(&[
458
- "e2e",
459
- "--providers",
460
- "fake",
461
- "--workspace",
462
- &ws.to_string_lossy(),
463
- "--json",
464
- ]),
465
- &ws,
466
- );
467
- assert_eq!(
468
- code,
469
- ExitCode::Ok,
470
- "e2e --providers fake must ROUTE + run the fake end-to-end (ok:true)"
471
- );
472
- let _ = std::fs::remove_dir_all(&ws);
473
- }
474
-
475
- // ── peek (#[ignore] real-machine) ── golden parser.py:201 `cmd_peek` (commands.py:118). Requires
476
- // `--allow-raw-screen` (else TeamAgentError) + a mutually-exclusive --head/--tail/--search; then
477
- // runtime.peek captures a LIVE tmux pane (tmux capture-pane). Real tmux -> #[ignore]. Routes to
478
- // cmd_peek; --json returns the peek dict (text + capture metadata).
479
- #[test]
480
- #[ignore = "real-machine: `peek <agent> --tail N --allow-raw-screen` captures a LIVE tmux pane \
481
- (tmux capture-pane); needs a running worker pane. Routes to cmd_peek (parser.py:201); \
482
- --json returns the peek dict (raw screen text + metadata)"]
483
- fn dispatch_routes_peek_real_machine() {
484
- let ws = tmp_workspace();
485
- let code = run(
486
- &cli_argv(&[
487
- "peek",
488
- "fake_impl",
489
- "--workspace",
490
- &ws.to_string_lossy(),
491
- "--tail",
492
- "20",
493
- "--allow-raw-screen",
494
- "--json",
495
- ]),
496
- &ws,
497
- );
498
- assert_eq!(
499
- code,
500
- ExitCode::Ok,
501
- "peek must ROUTE + capture the live pane (real machine)"
502
- );
503
- let _ = std::fs::remove_dir_all(&ws);
504
- }
505
-
506
249
  // CONTRACT (shared-root, real-machine-driven; golden = correct-behavior baseline): wait_readiness
507
250
  // derives cli_prompt_ready from the LIFECYCLE status — an alive worker (status="running") IS
508
251
  // cli_prompt_ready (golden quick_start.py:173 `status ∈ {running, busy}`). Rust (diagnose.rs:280)
@@ -129,11 +129,9 @@ mod leader_watch;
129
129
  mod main_preserved;
130
130
  mod missing_subcommands;
131
131
  mod named_address;
132
- mod peer_allow;
133
132
  mod run_delegation;
134
133
  mod shutdown_kill_plan;
135
134
  mod status_brief;
136
135
  mod status_send;
137
136
  mod verb_install_skill;
138
137
  mod verb_profile;
139
- mod verb_validate;
@@ -37,8 +37,8 @@ pub enum CliError {
37
37
  /// 委派的 runtime/lifecycle/state 错误(对应 Python `TeamAgentError`)。message == `str(exc)`。
38
38
  #[error("{0}")]
39
39
  Runtime(String),
40
- /// argparse 风格用法错误 / 互斥违反(如 `--summary` + `--json`、`--fix` 缺 `--gate`、
41
- /// `peek` 缺 `--allow-raw-screen`)。对应 Python 抛 `TeamAgentError` 或 parser.error。
40
+ /// argparse 风格用法错误 / 互斥违反(如 `--summary` + `--json`、`--fix` 缺 `--gate`)。
41
+ /// 对应 Python 抛 `TeamAgentError` 或 parser.error。
42
42
  #[error("usage error: {0}")]
43
43
  Usage(String),
44
44
  /// Preserve the original refusal for audit while carrying safe human guidance.
@@ -127,7 +127,6 @@ impl CliError {
127
127
  }
128
128
 
129
129
  /// CLI 进程退出码(`main`:`result.ok is False` 或异常 → `SystemExit(1)`,否则 0)。
130
- /// `watch` 路径直接 `SystemExit(0)`(`cmd_watch`)。
131
130
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
132
131
  pub enum ExitCode {
133
132
  Ok,
@@ -153,14 +152,14 @@ impl ExitCode {
153
152
  /// Python `cmd_*` 返回 `dict | str | None`:
154
153
  /// - `Json(Value)` ← 委派 runtime 的稳定 `{ok, ...}` dict(`--json` 序列化 sort_keys+indent=2)。
155
154
  /// - `Human(String)` ← 人读渲染(`format_status`/`cmd_advanced`/五行 summary/comms boundary text)。
156
- /// - `None` ← passthrough/watch 命令(`cmd_codex`/`cmd_claude`/`cmd_watch`,直接 SystemExit,无 emit)。
155
+ /// - `None` ← passthrough 命令(`cmd_codex`/`cmd_claude`,直接 SystemExit,无 emit)。
157
156
  #[derive(Debug, Clone, PartialEq)]
158
157
  pub enum CmdOutput {
159
158
  /// 机器可读稳定 dict(`--json` 路径或 `result.get("ok") is False` 路径)。
160
159
  Json(Value),
161
160
  /// 人读字符串(`emit` 非 dict 分支直接 print)。
162
161
  Human(String),
163
- /// 无输出(passthrough/watch;不经 `emit`)。
162
+ /// 无输出(passthrough;不经 `emit`)。
164
163
  None,
165
164
  }
166
165
 
@@ -193,19 +192,6 @@ impl CmdResult {
193
192
  preserve_json_order: false,
194
193
  }
195
194
  }
196
- pub fn from_ordered_json(value: Value) -> Self {
197
- let exit = if value.get("ok").and_then(Value::as_bool) == Some(false) {
198
- ExitCode::Error
199
- } else {
200
- ExitCode::Ok
201
- };
202
- Self {
203
- output: CmdOutput::Json(value),
204
- exit,
205
- as_json: true,
206
- preserve_json_order: true,
207
- }
208
- }
209
195
  pub fn human(text: impl Into<String>) -> Self {
210
196
  Self {
211
197
  output: CmdOutput::Human(text.into()),
@@ -357,18 +343,6 @@ pub struct SendArgs {
357
343
  pub to_leader: Option<String>,
358
344
  }
359
345
 
360
- /// `allow-peer-talk`(`parser.py`): allow direct peer communication between two agents.
361
- #[derive(Debug, Clone, PartialEq, Eq)]
362
- pub struct AllowPeerTalkArgs {
363
- pub a: String,
364
- pub b: String,
365
- pub workspace: PathBuf,
366
- pub json: bool,
367
- /// Parsed but rejected until peer allowlist storage grows a team-scoped
368
- /// backend column.
369
- pub team: Option<String>,
370
- }
371
-
372
346
  /// `status`(`parser.py:182`)。`--summary`/`--json`/`--detail` 三态(summary xor json,见 `cmd_status`)。
373
347
  #[derive(Debug, Clone, PartialEq, Eq)]
374
348
  pub struct StatusArgs {
@@ -382,13 +356,6 @@ pub struct StatusArgs {
382
356
  pub team: Option<String>,
383
357
  }
384
358
 
385
- /// `watch`(`parser.py:190`)。无 `--json`(纯 stream 到 SystemExit)。
386
- #[derive(Debug, Clone, PartialEq, Eq)]
387
- pub struct WatchArgs {
388
- pub workspace: PathBuf,
389
- pub team: Option<String>,
390
- }
391
-
392
359
  /// `approvals`(`parser.py:195`)。
393
360
  #[derive(Debug, Clone, PartialEq, Eq)]
394
361
  pub struct ApprovalsArgs {
@@ -452,14 +419,6 @@ pub struct AttachAppServerLeaderArgs {
452
419
  pub json: bool,
453
420
  }
454
421
 
455
- /// `identity`(`parser.py:256`)。
456
- #[derive(Debug, Clone, PartialEq, Eq)]
457
- pub struct IdentityArgs {
458
- pub workspace: PathBuf,
459
- pub team: Option<String>,
460
- pub json: bool,
461
- }
462
-
463
422
  /// `shutdown`(`parser.py:355`)。`--keep-logs` 默认 true。
464
423
  #[derive(Debug, Clone, PartialEq, Eq)]
465
424
  pub struct ShutdownArgs {
@@ -584,15 +543,6 @@ pub enum DoctorGate {
584
543
  Unknown(String),
585
544
  }
586
545
 
587
- /// `sessions`(`parser.py:230`)。
588
- #[derive(Debug, Clone, PartialEq, Eq)]
589
- pub struct SessionsArgs {
590
- pub workspace: PathBuf,
591
- pub json: bool,
592
- /// Stage 4: explicit `--team` scope (read-only enumeration).
593
- pub team: Option<String>,
594
- }
595
-
596
546
  /// E7 (0.5.9 host-leader-registry-design §4.1): `team-agent leaders`
597
547
  /// enumerates the host discovery index and classifies each entry as
598
548
  /// LIVE, STALE, or AMBIGUOUS after re-validating against canonical
@@ -621,13 +571,6 @@ pub struct ModelsArgs {
621
571
  pub json: bool,
622
572
  }
623
573
 
624
- /// `validate [spec=team.spec.yaml] --json`(`parser.py:120`)。
625
- #[derive(Debug, Clone, PartialEq, Eq)]
626
- pub struct ValidateArgs {
627
- pub spec: PathBuf,
628
- pub json: bool,
629
- }
630
-
631
574
  /// `profile {init,doctor,show}`(`parser.py:131`)。
632
575
  #[derive(Debug, Clone, PartialEq, Eq)]
633
576
  pub struct ProfileArgs {
@@ -640,56 +583,9 @@ pub struct ProfileArgs {
640
583
  pub json: bool,
641
584
  }
642
585
 
643
- /// Read-only lookup of all stored result envelopes for one case.
644
- #[derive(Debug, Clone, PartialEq, Eq)]
645
- pub struct ResultsArgs {
646
- pub case_id: String,
647
- pub workspace: PathBuf,
648
- pub team: Option<String>,
649
- pub json: bool,
650
- }
651
-
652
586
  #[derive(Debug, Clone, PartialEq, Eq)]
653
587
  pub struct WaitArgs {
654
588
  pub task_id: String,
655
589
  pub workspace: PathBuf,
656
590
  pub json: bool,
657
591
  }
658
-
659
- /// `preflight`(`parser.py:160`)。
660
- #[derive(Debug, Clone, PartialEq, Eq)]
661
- pub struct PreflightArgs {
662
- pub team: PathBuf,
663
- pub json: bool,
664
- }
665
-
666
- /// `wait-ready`(`parser.py:171`)。
667
- #[derive(Debug, Clone, PartialEq)]
668
- pub struct WaitReadyArgs {
669
- pub workspace: PathBuf,
670
- pub timeout: f64,
671
- pub json: bool,
672
- /// Stage 4: explicit `--team` scope (read-only readiness probe).
673
- pub team: Option<String>,
674
- }
675
-
676
- /// `e2e`(`parser.py:449`)。
677
- #[derive(Debug, Clone, PartialEq, Eq)]
678
- pub struct E2eArgs {
679
- pub workspace: PathBuf,
680
- pub providers: Vec<String>,
681
- pub real: bool,
682
- pub json: bool,
683
- }
684
-
685
- /// `peek`(`parser.py:201`)。
686
- #[derive(Debug, Clone, PartialEq, Eq)]
687
- pub struct PeekArgs {
688
- pub agent: String,
689
- pub workspace: PathBuf,
690
- pub tail: usize,
691
- pub head: Option<usize>,
692
- pub search: Option<String>,
693
- pub allow_raw_screen: bool,
694
- pub json: bool,
695
- }