@team-agent/installer 0.5.31 → 0.5.32
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 +0 -313
- package/crates/team-agent/src/cli/diagnose.rs +1 -1
- package/crates/team-agent/src/cli/emit.rs +3 -157
- package/crates/team-agent/src/cli/send.rs +0 -241
- package/crates/team-agent/src/cli/spec.rs +0 -16
- package/crates/team-agent/src/cli/tests/main_preserved.rs +0 -60
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +4 -54
- package/crates/team-agent/src/cli/tests/mod.rs +0 -2
- package/crates/team-agent/src/cli/types.rs +1 -64
- package/crates/team-agent/src/coordinator/health.rs +1 -1
- package/crates/team-agent/src/coordinator/tick.rs +36 -1
- package/crates/team-agent/src/db/agent_health_capture.rs +24 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +14 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +27 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +27 -2
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +1 -1
- package/crates/team-agent/src/packaging/mod.rs +1 -1
- package/crates/team-agent/src/packaging/types.rs +1 -1
- package/package.json +4 -4
- package/skills/team-agent/references/recovery-runbook.md +5 -20
- package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +0 -374
- package/crates/team-agent/src/cli/tests/verb_settle.rs +0 -238
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
|
@@ -398,33 +398,6 @@ pub fn cmd_sessions(args: &SessionsArgs) -> Result<CmdResult, CliError> {
|
|
|
398
398
|
))
|
|
399
399
|
}
|
|
400
400
|
|
|
401
|
-
/// `cmd_validate_result`(`commands.py:206`)。
|
|
402
|
-
pub fn cmd_validate_result(args: &ValidateResultArgs) -> Result<CmdResult, CliError> {
|
|
403
|
-
let raw = if let Some(path) = &args.file {
|
|
404
|
-
std::fs::read_to_string(path)?
|
|
405
|
-
} else if let Some(envelope) = &args.envelope {
|
|
406
|
-
envelope.clone()
|
|
407
|
-
} else if let Some(result) = &args.result {
|
|
408
|
-
result.clone()
|
|
409
|
-
} else {
|
|
410
|
-
let mut input = String::new();
|
|
411
|
-
std::io::Read::read_to_string(&mut std::io::stdin(), &mut input)?;
|
|
412
|
-
input
|
|
413
|
-
};
|
|
414
|
-
let envelope: Value = serde_json::from_str(&raw)?;
|
|
415
|
-
crate::model::spec::validate_result_envelope(&envelope)
|
|
416
|
-
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
417
|
-
Ok(CmdResult::from_json(
|
|
418
|
-
json!({
|
|
419
|
-
"ok": true,
|
|
420
|
-
"task_id": envelope.get("task_id").cloned().unwrap_or(Value::Null),
|
|
421
|
-
"agent_id": envelope.get("agent_id").cloned().unwrap_or(Value::Null),
|
|
422
|
-
"status": envelope.get("status").cloned().unwrap_or(Value::Null),
|
|
423
|
-
}),
|
|
424
|
-
args.json,
|
|
425
|
-
))
|
|
426
|
-
}
|
|
427
|
-
|
|
428
401
|
/// `cmd_collect`(`parser.py:292`)。
|
|
429
402
|
pub fn cmd_collect(args: &CollectArgs) -> Result<CmdResult, CliError> {
|
|
430
403
|
cmd_collect_for_team(args, args.team.as_deref())
|
|
@@ -501,162 +474,6 @@ pub fn cmd_collect_for_team(args: &CollectArgs, team: Option<&str>) -> Result<Cm
|
|
|
501
474
|
))
|
|
502
475
|
}
|
|
503
476
|
|
|
504
|
-
/// `cmd_settle`(`commands.py:86`)。
|
|
505
|
-
pub fn cmd_settle(args: &SettleArgs) -> Result<CmdResult, CliError> {
|
|
506
|
-
match settle_value(&args.workspace, args.team.as_deref()) {
|
|
507
|
-
Ok(value) => Ok(CmdResult::from_json(value, args.json)),
|
|
508
|
-
Err(error) => Ok(CmdResult::from_json(
|
|
509
|
-
json!({
|
|
510
|
-
"ok": false,
|
|
511
|
-
"error": error.to_string(),
|
|
512
|
-
"workspace": args.workspace.to_string_lossy().to_string(),
|
|
513
|
-
}),
|
|
514
|
-
args.json,
|
|
515
|
-
)),
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
fn settle_value(workspace: &Path, team: Option<&str>) -> Result<Value, CliError> {
|
|
520
|
-
// Bug #6 (prerelease 0.4.0 gate review §6): pass explicit team through to
|
|
521
|
-
// resolve_active_team so settle scopes collect/status/team-state to the
|
|
522
|
-
// requested team. Without this, the selector falls back to top-level
|
|
523
|
-
// active_team_key and settle operates on the wrong team. resolve_active_team
|
|
524
|
-
// logic unchanged per 不可改项.
|
|
525
|
-
let selected = crate::state::selector::resolve_active_team(
|
|
526
|
-
workspace,
|
|
527
|
-
team,
|
|
528
|
-
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
529
|
-
)
|
|
530
|
-
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
531
|
-
let mut collect = messaging::collect_for_team(
|
|
532
|
-
&selected.run_workspace,
|
|
533
|
-
None,
|
|
534
|
-
false,
|
|
535
|
-
Some(&selected.team_key),
|
|
536
|
-
)?;
|
|
537
|
-
if collect.get("ok").and_then(Value::as_bool) == Some(false) {
|
|
538
|
-
let message = collect
|
|
539
|
-
.get("error")
|
|
540
|
-
.and_then(Value::as_str)
|
|
541
|
-
.unwrap_or("collect failed");
|
|
542
|
-
return Err(CliError::Runtime(message.to_string()));
|
|
543
|
-
}
|
|
544
|
-
let coordinator_log = crate::coordinator::coordinator_log_path(
|
|
545
|
-
&crate::coordinator::WorkspacePath::new(selected.run_workspace.clone()),
|
|
546
|
-
);
|
|
547
|
-
let collect_object = collect
|
|
548
|
-
.as_object_mut()
|
|
549
|
-
.ok_or_else(|| CliError::Runtime("collect returned non-object output".to_string()))?;
|
|
550
|
-
collect_object.insert(
|
|
551
|
-
"coordinator".to_string(),
|
|
552
|
-
json!({
|
|
553
|
-
"ok": true,
|
|
554
|
-
"status": "started",
|
|
555
|
-
"log": coordinator_log.to_string_lossy().to_string(),
|
|
556
|
-
}),
|
|
557
|
-
);
|
|
558
|
-
collect_object.insert("team_key".to_string(), json!(selected.team_key.clone()));
|
|
559
|
-
collect_object.insert("active_team_key".to_string(), json!(selected.team_key.clone()));
|
|
560
|
-
collect_object.insert("team".to_string(), json!(selected.team_key.clone()));
|
|
561
|
-
if let Some(collected_results) = collect_object.get("collected_results").cloned() {
|
|
562
|
-
collect_object.insert("collected".to_string(), collected_results);
|
|
563
|
-
}
|
|
564
|
-
let status_state =
|
|
565
|
-
crate::state::projection::select_runtime_state(&selected.run_workspace, Some(&selected.team_key))?;
|
|
566
|
-
let state_file = match (selected.spec_path.as_ref(), selected.spec_workspace.as_ref()) {
|
|
567
|
-
(Some(spec_path), Some(spec_workspace)) => match load_team_spec_at(spec_path)? {
|
|
568
|
-
Some(spec) => crate::lifecycle::restart::write_team_state(spec_workspace, &spec, &status_state)
|
|
569
|
-
.map_err(|e| CliError::Runtime(e.to_string()))?
|
|
570
|
-
.to_string_lossy()
|
|
571
|
-
.to_string(),
|
|
572
|
-
None => collect
|
|
573
|
-
.get("state_file")
|
|
574
|
-
.and_then(Value::as_str)
|
|
575
|
-
.unwrap_or("")
|
|
576
|
-
.to_string(),
|
|
577
|
-
},
|
|
578
|
-
_ => collect
|
|
579
|
-
.get("state_file")
|
|
580
|
-
.and_then(Value::as_str)
|
|
581
|
-
.unwrap_or("")
|
|
582
|
-
.to_string(),
|
|
583
|
-
};
|
|
584
|
-
if let Some(obj) = collect.as_object_mut() {
|
|
585
|
-
obj.insert("state_file".to_string(), json!(state_file.clone()));
|
|
586
|
-
}
|
|
587
|
-
let mut status = status_port::status_scoped(
|
|
588
|
-
&selected.run_workspace,
|
|
589
|
-
&status_state,
|
|
590
|
-
Some(&selected.team_key),
|
|
591
|
-
true,
|
|
592
|
-
false,
|
|
593
|
-
)?;
|
|
594
|
-
if let Some(obj) = status.as_object_mut() {
|
|
595
|
-
obj.insert("team_key".to_string(), json!(selected.team_key.clone()));
|
|
596
|
-
obj.insert("active_team_key".to_string(), json!(selected.team_key.clone()));
|
|
597
|
-
obj.insert("team".to_string(), json!(selected.team_key.clone()));
|
|
598
|
-
}
|
|
599
|
-
let details_log = write_settle_details_log(&selected.run_workspace, &collect, &status)?;
|
|
600
|
-
let collected_count = collect
|
|
601
|
-
.get("collected")
|
|
602
|
-
.and_then(Value::as_array)
|
|
603
|
-
.map_or(0, Vec::len);
|
|
604
|
-
let settled_results = settle_collected_results_for_team(
|
|
605
|
-
collect.get("collected_results"),
|
|
606
|
-
&selected.team_key,
|
|
607
|
-
);
|
|
608
|
-
Ok(json!({
|
|
609
|
-
"ok": true,
|
|
610
|
-
"summary": format!("collected {collected_count} result(s)"),
|
|
611
|
-
"next_actions": ["Review team_state.md and decide whether to continue or shutdown."],
|
|
612
|
-
"details_log": details_log.to_string_lossy().to_string(),
|
|
613
|
-
"collected_results": settled_results,
|
|
614
|
-
"collected": collect.get("collected").cloned().unwrap_or_else(|| json!([])),
|
|
615
|
-
"results": collect.get("results").cloned().unwrap_or_else(|| json!({})),
|
|
616
|
-
"state_file": state_file,
|
|
617
|
-
"status": status,
|
|
618
|
-
"collect": collect,
|
|
619
|
-
"team_key": selected.team_key,
|
|
620
|
-
"active_team_key": selected.team_key,
|
|
621
|
-
"team": selected.team_key,
|
|
622
|
-
"workspace": selected.run_workspace.to_string_lossy().to_string(),
|
|
623
|
-
}))
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
fn settle_collected_results_for_team(value: Option<&Value>, team_key: &str) -> Value {
|
|
627
|
-
let Some(Value::Array(results)) = value else {
|
|
628
|
-
return json!([]);
|
|
629
|
-
};
|
|
630
|
-
Value::Array(
|
|
631
|
-
results
|
|
632
|
-
.iter()
|
|
633
|
-
.map(|result| {
|
|
634
|
-
let mut result = result.clone();
|
|
635
|
-
if let Some(obj) = result.as_object_mut() {
|
|
636
|
-
obj.insert("owner_team_id".to_string(), json!(team_key));
|
|
637
|
-
}
|
|
638
|
-
result
|
|
639
|
-
})
|
|
640
|
-
.collect(),
|
|
641
|
-
)
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
fn write_settle_details_log(workspace: &Path, collect: &Value, status: &Value) -> Result<PathBuf, CliError> {
|
|
645
|
-
let logs = workspace.join(".team").join("logs");
|
|
646
|
-
std::fs::create_dir_all(&logs)?;
|
|
647
|
-
let timestamp = std::time::SystemTime::now()
|
|
648
|
-
.duration_since(std::time::UNIX_EPOCH)
|
|
649
|
-
.map_or(0, |duration| duration.as_secs());
|
|
650
|
-
let path = logs.join(format!("settle-{timestamp}.json"));
|
|
651
|
-
let details = json!({
|
|
652
|
-
"collect": collect,
|
|
653
|
-
"status": status,
|
|
654
|
-
});
|
|
655
|
-
let text = serde_json::to_string_pretty(&crate::cli::sort_json(&details))?;
|
|
656
|
-
std::fs::write(&path, text)?;
|
|
657
|
-
Ok(path)
|
|
658
|
-
}
|
|
659
|
-
|
|
660
477
|
/// `cmd_allow_peer_talk`(`parser.py allow-peer-talk`).
|
|
661
478
|
pub fn cmd_allow_peer_talk(args: &AllowPeerTalkArgs) -> Result<CmdResult, CliError> {
|
|
662
479
|
if args.team.is_some() {
|
|
@@ -668,65 +485,6 @@ pub fn cmd_allow_peer_talk(args: &AllowPeerTalkArgs) -> Result<CmdResult, CliErr
|
|
|
668
485
|
Ok(CmdResult::from_json(value, args.json))
|
|
669
486
|
}
|
|
670
487
|
|
|
671
|
-
/// `cmd_repair_state`(`parser.py:303`)。
|
|
672
|
-
pub fn cmd_repair_state(args: &RepairStateArgs) -> Result<CmdResult, CliError> {
|
|
673
|
-
if !is_repair_task_status(&args.status) {
|
|
674
|
-
return Err(CliError::Runtime(format!(
|
|
675
|
-
"unknown task status for repair: {}",
|
|
676
|
-
args.status
|
|
677
|
-
)));
|
|
678
|
-
}
|
|
679
|
-
let selected = crate::state::selector::resolve_active_team(
|
|
680
|
-
&args.workspace,
|
|
681
|
-
args.team.as_deref(),
|
|
682
|
-
crate::state::selector::SelectorMode::RequireSpec,
|
|
683
|
-
)
|
|
684
|
-
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
685
|
-
let mut state = selected.state;
|
|
686
|
-
let before = find_task_projection(&state, &args.task_id).unwrap_or_else(repair_task_projection_null);
|
|
687
|
-
update_task(
|
|
688
|
-
&mut state,
|
|
689
|
-
&args.task_id,
|
|
690
|
-
args.assignee.as_deref(),
|
|
691
|
-
&args.status,
|
|
692
|
-
args.summary.as_deref(),
|
|
693
|
-
);
|
|
694
|
-
let after = find_task_projection(&state, &args.task_id).unwrap_or_else(repair_task_projection_null);
|
|
695
|
-
crate::state::projection::save_team_scoped_state(&selected.run_workspace, &state)?;
|
|
696
|
-
let spec_path = selected
|
|
697
|
-
.spec_path
|
|
698
|
-
.as_ref()
|
|
699
|
-
.ok_or_else(|| CliError::Runtime("team.spec.yaml not found".to_string()))?;
|
|
700
|
-
let spec = load_team_spec_at(spec_path)?
|
|
701
|
-
.ok_or_else(|| CliError::Runtime("team.spec.yaml not found".to_string()))?;
|
|
702
|
-
let spec_workspace = selected
|
|
703
|
-
.spec_workspace
|
|
704
|
-
.as_ref()
|
|
705
|
-
.ok_or_else(|| CliError::Runtime("active team spec workspace not found".to_string()))?;
|
|
706
|
-
let state_file = crate::lifecycle::restart::write_team_state(spec_workspace, &spec, &state)
|
|
707
|
-
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
708
|
-
crate::event_log::EventLog::new(&selected.run_workspace)
|
|
709
|
-
.write(
|
|
710
|
-
"repair_state.task",
|
|
711
|
-
json!({
|
|
712
|
-
"task_id": args.task_id,
|
|
713
|
-
"before": before,
|
|
714
|
-
"after": after,
|
|
715
|
-
}),
|
|
716
|
-
)
|
|
717
|
-
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
718
|
-
Ok(CmdResult::from_json(
|
|
719
|
-
json!({
|
|
720
|
-
"ok": true,
|
|
721
|
-
"task_id": args.task_id,
|
|
722
|
-
"before": before,
|
|
723
|
-
"after": after,
|
|
724
|
-
"state_file": state_file.to_string_lossy().to_string(),
|
|
725
|
-
}),
|
|
726
|
-
args.json,
|
|
727
|
-
))
|
|
728
|
-
}
|
|
729
|
-
|
|
730
488
|
/// `cmd_diagnose`(`parser.py:298`)。
|
|
731
489
|
pub fn cmd_diagnose(args: &DiagnoseArgs) -> Result<CmdResult, CliError> {
|
|
732
490
|
let selected = crate::state::selector::resolve_active_team(
|
|
@@ -1310,77 +1068,6 @@ fn window_target(agent_state: &Value, agent_id: &str) -> Value {
|
|
|
1310
1068
|
}
|
|
1311
1069
|
|
|
1312
1070
|
|
|
1313
|
-
fn find_task_projection(state: &Value, task_id: &str) -> Option<Value> {
|
|
1314
|
-
state
|
|
1315
|
-
.get("tasks")
|
|
1316
|
-
.and_then(Value::as_array)
|
|
1317
|
-
.and_then(|tasks| tasks.iter().find(|task| task.get("id").and_then(Value::as_str) == Some(task_id)))
|
|
1318
|
-
.map(repair_task_projection)
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
fn update_task(
|
|
1322
|
-
state: &mut Value,
|
|
1323
|
-
task_id: &str,
|
|
1324
|
-
assignee: Option<&str>,
|
|
1325
|
-
status: &str,
|
|
1326
|
-
summary: Option<&str>,
|
|
1327
|
-
) -> Value {
|
|
1328
|
-
if let Some(tasks) = state.get_mut("tasks").and_then(Value::as_array_mut) {
|
|
1329
|
-
for task in tasks {
|
|
1330
|
-
if task.get("id").and_then(Value::as_str) == Some(task_id) {
|
|
1331
|
-
if let Some(obj) = task.as_object_mut() {
|
|
1332
|
-
if let Some(assignee) = assignee {
|
|
1333
|
-
obj.insert("assignee".to_string(), Value::String(assignee.to_string()));
|
|
1334
|
-
}
|
|
1335
|
-
obj.insert("status".to_string(), Value::String(status.to_string()));
|
|
1336
|
-
if let Some(summary) = summary {
|
|
1337
|
-
obj.insert("last_result_summary".to_string(), Value::String(summary.to_string()));
|
|
1338
|
-
}
|
|
1339
|
-
return Value::Object(obj.clone());
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
json!({
|
|
1345
|
-
"id": task_id,
|
|
1346
|
-
"assignee": assignee.unwrap_or(""),
|
|
1347
|
-
"status": status,
|
|
1348
|
-
"summary": summary.unwrap_or(""),
|
|
1349
|
-
})
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
fn is_repair_task_status(status: &str) -> bool {
|
|
1353
|
-
matches!(
|
|
1354
|
-
status,
|
|
1355
|
-
"blocked" | "cancelled" | "done" | "failed" | "needs_retry" | "pending" | "ready" | "running"
|
|
1356
|
-
)
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
fn repair_task_projection(task: &Value) -> Value {
|
|
1360
|
-
let mut map = serde_json::Map::new();
|
|
1361
|
-
map.insert(
|
|
1362
|
-
"assignee".to_string(),
|
|
1363
|
-
task.get("assignee").cloned().unwrap_or(Value::Null),
|
|
1364
|
-
);
|
|
1365
|
-
map.insert(
|
|
1366
|
-
"status".to_string(),
|
|
1367
|
-
task.get("status").cloned().unwrap_or(Value::Null),
|
|
1368
|
-
);
|
|
1369
|
-
map.insert(
|
|
1370
|
-
"last_result_summary".to_string(),
|
|
1371
|
-
task.get("last_result_summary").cloned().unwrap_or(Value::Null),
|
|
1372
|
-
);
|
|
1373
|
-
Value::Object(map)
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
fn repair_task_projection_null() -> Value {
|
|
1377
|
-
let mut map = serde_json::Map::new();
|
|
1378
|
-
map.insert("assignee".to_string(), Value::Null);
|
|
1379
|
-
map.insert("status".to_string(), Value::Null);
|
|
1380
|
-
map.insert("last_result_summary".to_string(), Value::Null);
|
|
1381
|
-
Value::Object(map)
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
1071
|
fn load_team_spec_optional(workspace: &Path, state: &Value) -> Result<Option<crate::model::yaml::Value>, CliError> {
|
|
1385
1072
|
let spec_path = state
|
|
1386
1073
|
.get("spec_path")
|
|
@@ -276,7 +276,7 @@ fn coordinator_issue_value(
|
|
|
276
276
|
|
|
277
277
|
fn coordinator_repair_hint(id: &str, _health: &crate::coordinator::HealthReport) -> Value {
|
|
278
278
|
let hint_action = match id {
|
|
279
|
-
"coordinator_schema_incompatible" => "team-agent
|
|
279
|
+
"coordinator_schema_incompatible" => "team-agent doctor --fix-schema --json",
|
|
280
280
|
_ => "team-agent restart",
|
|
281
281
|
};
|
|
282
282
|
json!({
|
|
@@ -96,12 +96,6 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
|
|
|
96
96
|
"quick-start" => cmd_quick_start(&quick_start_args(args, cwd)?).map(emit_result),
|
|
97
97
|
"compile" => cmd_compile(&compile_args(args, cwd)?).map(emit_result),
|
|
98
98
|
"send" => cmd_send(&send_args(args, cwd)?).map(emit_result),
|
|
99
|
-
"fallback-send-leader" => {
|
|
100
|
-
cmd_fallback_send_leader(&fallback_send_leader_args(args, cwd)?).map(emit_result)
|
|
101
|
-
}
|
|
102
|
-
"fallback-report-result" => {
|
|
103
|
-
cmd_fallback_report_result(&fallback_report_result_args(args, cwd)?).map(emit_result)
|
|
104
|
-
}
|
|
105
99
|
"allow-peer-talk" => {
|
|
106
100
|
cmd_allow_peer_talk(&allow_peer_talk_args(args, cwd)?).map(emit_result)
|
|
107
101
|
}
|
|
@@ -147,17 +141,10 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
|
|
|
147
141
|
"validate" => cmd_validate(&validate_args(args, cwd)).map(emit_result),
|
|
148
142
|
"install-skill" => cmd_install_skill(&install_skill_args(args)?).map(emit_result),
|
|
149
143
|
"profile" => cmd_profile(&profile_args(args, cwd)?).map(emit_result),
|
|
150
|
-
"validate-result" if has_arg(args, "--result") => {
|
|
151
|
-
eprintln!("team-agent: error: unrecognized arguments: --result");
|
|
152
|
-
Ok(ExitCode::Usage)
|
|
153
|
-
}
|
|
154
|
-
"validate-result" => cmd_validate_result(&validate_result_args(args)?).map(emit_result),
|
|
155
144
|
"collect" => {
|
|
156
145
|
cmd_collect_for_team(&collect_args(args, cwd)?, parse_args(args).team.as_deref())
|
|
157
146
|
.map(emit_result)
|
|
158
147
|
}
|
|
159
|
-
"settle" => cmd_settle(&settle_args(args, cwd)).map(emit_result),
|
|
160
|
-
"repair-state" => cmd_repair_state(&repair_state_args(args, cwd)?).map(emit_result),
|
|
161
148
|
"diagnose" => cmd_diagnose(&diagnose_args(args, cwd)).map(emit_result),
|
|
162
149
|
"preflight" => cmd_preflight(&preflight_args(args, cwd)).map(emit_result),
|
|
163
150
|
"wait-ready" => cmd_wait_ready(&wait_ready_args(args, cwd)).map(emit_result),
|
|
@@ -173,8 +160,6 @@ const DISPATCH_COMMANDS: &[&str] = &[
|
|
|
173
160
|
"quick-start",
|
|
174
161
|
"compile",
|
|
175
162
|
"send",
|
|
176
|
-
"fallback-send-leader",
|
|
177
|
-
"fallback-report-result",
|
|
178
163
|
"allow-peer-talk",
|
|
179
164
|
"status",
|
|
180
165
|
"stop",
|
|
@@ -205,10 +190,7 @@ const DISPATCH_COMMANDS: &[&str] = &[
|
|
|
205
190
|
"validate",
|
|
206
191
|
"install-skill",
|
|
207
192
|
"profile",
|
|
208
|
-
"validate-result",
|
|
209
193
|
"collect",
|
|
210
|
-
"settle",
|
|
211
|
-
"repair-state",
|
|
212
194
|
"diagnose",
|
|
213
195
|
"preflight",
|
|
214
196
|
"wait-ready",
|
|
@@ -327,8 +309,6 @@ fn command_help(command: Option<&str>) -> String {
|
|
|
327
309
|
Some("start") => compat_hidden_help("start", "usage: team-agent start [TEAMDIR] [--yes] [--fresh] [--json]"),
|
|
328
310
|
Some("compile") => "usage: team-agent compile --team TEAM [--out FILE] [--json]".to_string(),
|
|
329
311
|
Some("send") => "usage: team-agent send TARGET MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--targets AGENTS] [--to-name NAME] [--pane PANE] [--task TASK] [--sender SENDER] [--watch-result] [--requires-ack|--no-ack] [--no-wait] [--timeout SECONDS] [--confirm-human] [--message-id ID] [--json]\n\nMVP: name-based cross-workspace addressing assumes trusted local caller; no auth gate.".to_string(),
|
|
330
|
-
Some("fallback-send-leader") => compat_hidden_help("fallback-send-leader", "usage: team-agent fallback-send-leader --workspace WORKSPACE --team TEAM --sender AGENT --message-id ID --content TEXT --primary-error ERROR [--task TASK] [--json]\n\nEmergency one-shot fallback only after a leader-bound MCP send transport failure; restart-agent after use."),
|
|
331
|
-
Some("fallback-report-result") => compat_hidden_help("fallback-report-result", "usage: team-agent fallback-report-result --workspace WORKSPACE --team TEAM --agent-id AGENT --task-id TASK --result-json JSON --primary-error ERROR [--json]\n\nEmergency one-shot fallback only after a report_result MCP transport failure; persists the result DB row, then restart-agent after use."),
|
|
332
312
|
Some("allow-peer-talk") => "usage: team-agent allow-peer-talk A B [--workspace WORKSPACE] [--json]".to_string(),
|
|
333
313
|
Some("status") => "usage: team-agent status [AGENT] [--workspace WORKSPACE] [--team TEAM] [--summary|--json] [--detail]\n\n默认输出: worker,空闲|工作|错误;错误细分走 status --summary".to_string(),
|
|
334
314
|
Some("stop") => compat_hidden_help("stop", "usage: team-agent stop [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]"),
|
|
@@ -358,10 +338,7 @@ fn command_help(command: Option<&str>) -> String {
|
|
|
358
338
|
Some("validate") => "usage: team-agent validate [SPEC] [--json]".to_string(),
|
|
359
339
|
Some("install-skill") => "usage: team-agent install-skill (--source DIR | --uninstall) [--target codex|claude|copilot|all] [--dest DIR] [--dry-run] [--json]".to_string(),
|
|
360
340
|
Some("profile") => "usage: team-agent profile COMMAND NAME [--workspace WORKSPACE] [--team TEAM] [--auth-mode MODE] [--json]".to_string(),
|
|
361
|
-
Some("validate-result") => "usage: team-agent validate-result [ENVELOPE] [--file FILE|--result JSON] [--json]".to_string(),
|
|
362
341
|
Some("collect") => "usage: team-agent collect [--workspace WORKSPACE] [--team TEAM] [--result-file FILE] [--json]".to_string(),
|
|
363
|
-
Some("settle") => "usage: team-agent settle [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
|
|
364
|
-
Some("repair-state") => "usage: team-agent repair-state --task TASK --status STATUS [SUMMARY] [--assignee AGENT] [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
|
|
365
342
|
Some("diagnose") => "usage: team-agent diagnose [--workspace WORKSPACE] [--team TEAM] [--json]".to_string(),
|
|
366
343
|
Some("preflight") => "usage: team-agent preflight [TEAMDIR] [--json]".to_string(),
|
|
367
344
|
Some("wait-ready") => "usage: team-agent wait-ready [--workspace WORKSPACE] [--team TEAM] [--timeout SECONDS] [--json]".to_string(),
|
|
@@ -1059,60 +1036,6 @@ fn send_args(args: &[String], cwd: &Path) -> Result<SendArgs, CliError> {
|
|
|
1059
1036
|
})
|
|
1060
1037
|
}
|
|
1061
1038
|
|
|
1062
|
-
fn fallback_send_leader_args(
|
|
1063
|
-
args: &[String],
|
|
1064
|
-
cwd: &Path,
|
|
1065
|
-
) -> Result<FallbackSendLeaderArgs, CliError> {
|
|
1066
|
-
let parsed = parse_args(args);
|
|
1067
|
-
Ok(FallbackSendLeaderArgs {
|
|
1068
|
-
workspace: workspace(&parsed, cwd),
|
|
1069
|
-
team: parsed.team,
|
|
1070
|
-
sender: parsed
|
|
1071
|
-
.sender
|
|
1072
|
-
.or(parsed.agent_id)
|
|
1073
|
-
.ok_or_else(|| CliError::Usage("missing --sender <agent>".to_string()))?,
|
|
1074
|
-
task: parsed.task.or(parsed.task_id),
|
|
1075
|
-
message_id: parsed
|
|
1076
|
-
.message_id
|
|
1077
|
-
.ok_or_else(|| CliError::Usage("missing --message-id <id>".to_string()))?,
|
|
1078
|
-
content: parsed
|
|
1079
|
-
.content
|
|
1080
|
-
.or_else(|| (!parsed.positionals.is_empty()).then(|| parsed.positionals.join(" ")))
|
|
1081
|
-
.ok_or_else(|| CliError::Usage("missing --content <text>".to_string()))?,
|
|
1082
|
-
primary_error: parsed
|
|
1083
|
-
.primary_error
|
|
1084
|
-
.ok_or_else(|| CliError::Usage("missing --primary-error <error>".to_string()))?,
|
|
1085
|
-
json: parsed.json,
|
|
1086
|
-
})
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
fn fallback_report_result_args(
|
|
1090
|
-
args: &[String],
|
|
1091
|
-
cwd: &Path,
|
|
1092
|
-
) -> Result<FallbackReportResultArgs, CliError> {
|
|
1093
|
-
let parsed = parse_args(args);
|
|
1094
|
-
Ok(FallbackReportResultArgs {
|
|
1095
|
-
workspace: workspace(&parsed, cwd),
|
|
1096
|
-
team: parsed.team,
|
|
1097
|
-
agent_id: parsed
|
|
1098
|
-
.agent_id
|
|
1099
|
-
.or(parsed.sender)
|
|
1100
|
-
.ok_or_else(|| CliError::Usage("missing --agent-id <agent>".to_string()))?,
|
|
1101
|
-
task_id: parsed
|
|
1102
|
-
.task_id
|
|
1103
|
-
.or(parsed.task)
|
|
1104
|
-
.ok_or_else(|| CliError::Usage("missing --task-id <task>".to_string()))?,
|
|
1105
|
-
result_json: parsed
|
|
1106
|
-
.result_json
|
|
1107
|
-
.or_else(|| parsed.result)
|
|
1108
|
-
.ok_or_else(|| CliError::Usage("missing --result-json <json>".to_string()))?,
|
|
1109
|
-
primary_error: parsed
|
|
1110
|
-
.primary_error
|
|
1111
|
-
.ok_or_else(|| CliError::Usage("missing --primary-error <error>".to_string()))?,
|
|
1112
|
-
json: parsed.json,
|
|
1113
|
-
})
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
1039
|
/// Stage 4 of identity-boundary unified plan (architect direction
|
|
1117
1040
|
/// 2026-06-24, .team/artifacts/identity-boundary-unified-plan.md §2 Stage
|
|
1118
1041
|
/// 4): destructive command ambiguity gate. When the workspace has 2+
|
|
@@ -1471,16 +1394,6 @@ fn profile_args(args: &[String], cwd: &Path) -> Result<ProfileArgs, CliError> {
|
|
|
1471
1394
|
})
|
|
1472
1395
|
}
|
|
1473
1396
|
|
|
1474
|
-
fn validate_result_args(args: &[String]) -> Result<ValidateResultArgs, CliError> {
|
|
1475
|
-
let parsed = parse_args(args);
|
|
1476
|
-
Ok(ValidateResultArgs {
|
|
1477
|
-
envelope: parsed.positionals.first().cloned(),
|
|
1478
|
-
file: parsed.file,
|
|
1479
|
-
result: parsed.result,
|
|
1480
|
-
json: parsed.json,
|
|
1481
|
-
})
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
1397
|
fn collect_args(args: &[String], cwd: &Path) -> Result<CollectArgs, CliError> {
|
|
1485
1398
|
let parsed = parse_args(args);
|
|
1486
1399
|
let workspace = workspace(&parsed, cwd);
|
|
@@ -1493,34 +1406,6 @@ fn collect_args(args: &[String], cwd: &Path) -> Result<CollectArgs, CliError> {
|
|
|
1493
1406
|
})
|
|
1494
1407
|
}
|
|
1495
1408
|
|
|
1496
|
-
fn settle_args(args: &[String], cwd: &Path) -> SettleArgs {
|
|
1497
|
-
let parsed = parse_args(args);
|
|
1498
|
-
SettleArgs {
|
|
1499
|
-
workspace: workspace(&parsed, cwd),
|
|
1500
|
-
team: parsed.team.clone(),
|
|
1501
|
-
json: parsed.json,
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
fn repair_state_args(args: &[String], cwd: &Path) -> Result<RepairStateArgs, CliError> {
|
|
1506
|
-
let parsed = parse_args(args);
|
|
1507
|
-
let workspace = workspace(&parsed, cwd);
|
|
1508
|
-
refuse_if_multi_alive_team_missing_scope("repair-state", &workspace, parsed.team.as_deref())?;
|
|
1509
|
-
Ok(RepairStateArgs {
|
|
1510
|
-
workspace,
|
|
1511
|
-
task_id: parsed
|
|
1512
|
-
.task
|
|
1513
|
-
.ok_or_else(|| CliError::Usage("missing --task".to_string()))?,
|
|
1514
|
-
assignee: parsed.assignee,
|
|
1515
|
-
status: parsed
|
|
1516
|
-
.status_value
|
|
1517
|
-
.ok_or_else(|| CliError::Usage("missing --status".to_string()))?,
|
|
1518
|
-
summary: option_value(args, "--summary").or_else(|| parsed.positionals.first().cloned()),
|
|
1519
|
-
json: parsed.json,
|
|
1520
|
-
team: parsed.team,
|
|
1521
|
-
})
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
1409
|
fn option_value(args: &[String], flag: &str) -> Option<String> {
|
|
1525
1410
|
let prefix = format!("{flag}=");
|
|
1526
1411
|
let mut i = 0usize;
|
|
@@ -1789,11 +1674,6 @@ mod tests {
|
|
|
1789
1674
|
let top_help = command_help(None);
|
|
1790
1675
|
let visible = visible_help_commands(&top_help);
|
|
1791
1676
|
for command in [
|
|
1792
|
-
"fallback-send-leader",
|
|
1793
|
-
"fallback-report-result",
|
|
1794
|
-
"repair-state",
|
|
1795
|
-
"settle",
|
|
1796
|
-
"validate-result",
|
|
1797
1677
|
"leaders",
|
|
1798
1678
|
"doctor",
|
|
1799
1679
|
"e2e",
|
|
@@ -1809,14 +1689,7 @@ mod tests {
|
|
|
1809
1689
|
|
|
1810
1690
|
#[test]
|
|
1811
1691
|
fn compat_hidden_help_has_sunset_action() {
|
|
1812
|
-
for command in [
|
|
1813
|
-
"fallback-send-leader",
|
|
1814
|
-
"fallback-report-result",
|
|
1815
|
-
"stop",
|
|
1816
|
-
"restart-agent",
|
|
1817
|
-
"start",
|
|
1818
|
-
"init",
|
|
1819
|
-
] {
|
|
1692
|
+
for command in ["stop", "restart-agent", "start", "init"] {
|
|
1820
1693
|
let help = command_help(Some(command)).to_lowercase();
|
|
1821
1694
|
assert!(help.contains("status: hidden compatibility command"));
|
|
1822
1695
|
assert!(help.contains("sunset: c2"));
|
|
@@ -1980,17 +1853,6 @@ mod tests {
|
|
|
1980
1853
|
&["--workspace", "--team", "--limit", "--since", "--json"][..],
|
|
1981
1854
|
),
|
|
1982
1855
|
("sessions", &["--workspace", "--team", "--json"][..]),
|
|
1983
|
-
(
|
|
1984
|
-
"repair-state",
|
|
1985
|
-
&[
|
|
1986
|
-
"--task",
|
|
1987
|
-
"--status",
|
|
1988
|
-
"--assignee",
|
|
1989
|
-
"--workspace",
|
|
1990
|
-
"--team",
|
|
1991
|
-
"--json",
|
|
1992
|
-
][..],
|
|
1993
|
-
),
|
|
1994
1856
|
("diagnose", &["--workspace", "--team", "--json"][..]),
|
|
1995
1857
|
(
|
|
1996
1858
|
"wait-ready",
|
|
@@ -2203,7 +2065,7 @@ mod tests {
|
|
|
2203
2065
|
fn refuse_helper_refuses_when_multi_alive_team_and_no_explicit_team() {
|
|
2204
2066
|
let ws = tmp_workspace();
|
|
2205
2067
|
seed_two_alive_teams_in(&ws);
|
|
2206
|
-
let err = refuse_if_multi_alive_team_missing_scope("
|
|
2068
|
+
let err = refuse_if_multi_alive_team_missing_scope("collect", &ws, None)
|
|
2207
2069
|
.expect_err("multi-alive-team must refuse without --team");
|
|
2208
2070
|
let message = err.to_string();
|
|
2209
2071
|
assert!(
|
|
@@ -2215,7 +2077,7 @@ mod tests {
|
|
|
2215
2077
|
"refusal must list candidate teams; got: {message}"
|
|
2216
2078
|
);
|
|
2217
2079
|
assert!(
|
|
2218
|
-
message.contains("
|
|
2080
|
+
message.contains("collect"),
|
|
2219
2081
|
"refusal must name the command for diagnostic clarity; got: {message}"
|
|
2220
2082
|
);
|
|
2221
2083
|
}
|
|
@@ -2244,22 +2106,6 @@ mod tests {
|
|
|
2244
2106
|
);
|
|
2245
2107
|
}
|
|
2246
2108
|
|
|
2247
|
-
#[test]
|
|
2248
|
-
fn repair_state_args_builder_refuses_on_multi_alive_team_before_task_validation() {
|
|
2249
|
-
let ws = tmp_workspace();
|
|
2250
|
-
seed_two_alive_teams_in(&ws);
|
|
2251
|
-
// The ambiguity gate must fire BEFORE `--task` / `--status` validation
|
|
2252
|
-
// so the operator sees the multi-team confusion before any other
|
|
2253
|
-
// usage error.
|
|
2254
|
-
let argv = cli_argv(&["--workspace", &ws.to_string_lossy()]);
|
|
2255
|
-
let err = repair_state_args(&argv, &ws).expect_err("must refuse");
|
|
2256
|
-
let message = err.to_string();
|
|
2257
|
-
assert!(
|
|
2258
|
-
message.contains("multiple alive teams"),
|
|
2259
|
-
"ambiguity gate must precede --task/--status validation; got: {message}"
|
|
2260
|
-
);
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2263
2109
|
#[test]
|
|
2264
2110
|
fn shutdown_args_builder_refuses_on_multi_alive_team() {
|
|
2265
2111
|
let ws = tmp_workspace();
|