@team-agent/installer 0.3.2 → 0.3.3
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 +34 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +196 -19
- package/crates/team-agent/src/cli/diagnose.rs +144 -10
- package/crates/team-agent/src/cli/emit.rs +286 -52
- package/crates/team-agent/src/cli/leader.rs +37 -8
- package/crates/team-agent/src/cli/mod.rs +799 -316
- package/crates/team-agent/src/cli/status_port.rs +25 -2
- package/crates/team-agent/src/cli/tests/divergence.rs +1 -2
- package/crates/team-agent/src/cli/tests/lane_c.rs +23 -13
- package/crates/team-agent/src/cli/tests/main_preserved.rs +2 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +57 -3
- package/crates/team-agent/src/cli/types.rs +17 -0
- package/crates/team-agent/src/compiler.rs +15 -5
- package/crates/team-agent/src/coordinator/health.rs +89 -20
- package/crates/team-agent/src/coordinator/mod.rs +4 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +500 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +58 -0
- package/crates/team-agent/src/coordinator/tick.rs +222 -69
- package/crates/team-agent/src/coordinator/types.rs +15 -3
- package/crates/team-agent/src/db/schema.rs +37 -2
- package/crates/team-agent/src/diagnose/comms.rs +226 -0
- package/crates/team-agent/src/diagnose/mod.rs +45 -0
- package/crates/team-agent/src/diagnose/orphans.rs +658 -0
- package/crates/team-agent/src/fake_worker.rs +146 -3
- package/crates/team-agent/src/leader/start.rs +121 -23
- package/crates/team-agent/src/leader/types.rs +44 -1
- package/crates/team-agent/src/lib.rs +3 -0
- package/crates/team-agent/src/lifecycle/display.rs +645 -47
- package/crates/team-agent/src/lifecycle/launch.rs +818 -116
- package/crates/team-agent/src/lifecycle/mod.rs +2 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +810 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +522 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +99 -23
- package/crates/team-agent/src/lifecycle/restart/common.rs +177 -83
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +443 -9
- package/crates/team-agent/src/lifecycle/restart/remove.rs +22 -6
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +19 -0
- package/crates/team-agent/src/lifecycle/restart.rs +4 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +5 -5
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +37 -7
- package/crates/team-agent/src/lifecycle/types.rs +19 -0
- package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +341 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +10 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +158 -0
- package/crates/team-agent/src/mcp_server/mod.rs +3 -74
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -1
- package/crates/team-agent/src/mcp_server/tests/send.rs +6 -5
- package/crates/team-agent/src/mcp_server/tools.rs +312 -111
- package/crates/team-agent/src/mcp_server/types.rs +6 -4
- package/crates/team-agent/src/mcp_server/wire.rs +19 -7
- package/crates/team-agent/src/message_store.rs +21 -4
- package/crates/team-agent/src/messaging/delivery.rs +87 -37
- package/crates/team-agent/src/messaging/mod.rs +9 -6
- package/crates/team-agent/src/messaging/results.rs +153 -16
- package/crates/team-agent/src/messaging/selftest.rs +199 -12
- package/crates/team-agent/src/messaging/send.rs +35 -3
- package/crates/team-agent/src/messaging/tests/runtime.rs +19 -4
- package/crates/team-agent/src/messaging/types.rs +11 -3
- package/crates/team-agent/src/os_probe.rs +119 -0
- package/crates/team-agent/src/packaging/migrate.rs +10 -2
- package/crates/team-agent/src/packaging/tests.rs +23 -0
- package/crates/team-agent/src/provider/adapter.rs +483 -67
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +1 -7
- package/crates/team-agent/src/provider/classify.rs +51 -4
- package/crates/team-agent/src/provider/startup_prompt.rs +94 -0
- package/crates/team-agent/src/provider/types.rs +47 -0
- package/crates/team-agent/src/session_capture.rs +616 -0
- package/crates/team-agent/src/state/persist.rs +57 -0
- package/crates/team-agent/src/state/projection.rs +32 -23
- package/crates/team-agent/src/state/selector.rs +5 -2
- package/crates/team-agent/src/tmux_backend.rs +97 -60
- package/crates/team-agent/src/transport/test_support.rs +9 -0
- package/crates/team-agent/src/transport/tests/wire.rs +4 -0
- package/crates/team-agent/src/transport.rs +13 -2
- package/package.json +4 -4
package/Cargo.lock
CHANGED
|
@@ -153,6 +153,16 @@ version = "0.1.9"
|
|
|
153
153
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
154
154
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
|
155
155
|
|
|
156
|
+
[[package]]
|
|
157
|
+
name = "fslock"
|
|
158
|
+
version = "0.2.1"
|
|
159
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
160
|
+
checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb"
|
|
161
|
+
dependencies = [
|
|
162
|
+
"libc",
|
|
163
|
+
"winapi",
|
|
164
|
+
]
|
|
165
|
+
|
|
156
166
|
[[package]]
|
|
157
167
|
name = "futures-core"
|
|
158
168
|
version = "0.3.32"
|
|
@@ -494,6 +504,7 @@ version = "3.5.0"
|
|
|
494
504
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
495
505
|
checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d"
|
|
496
506
|
dependencies = [
|
|
507
|
+
"fslock",
|
|
497
508
|
"futures-executor",
|
|
498
509
|
"futures-util",
|
|
499
510
|
"log",
|
|
@@ -555,7 +566,7 @@ dependencies = [
|
|
|
555
566
|
|
|
556
567
|
[[package]]
|
|
557
568
|
name = "team-agent"
|
|
558
|
-
version = "0.3.
|
|
569
|
+
version = "0.3.3"
|
|
559
570
|
dependencies = [
|
|
560
571
|
"anyhow",
|
|
561
572
|
"chrono",
|
|
@@ -658,6 +669,28 @@ dependencies = [
|
|
|
658
669
|
"unicode-ident",
|
|
659
670
|
]
|
|
660
671
|
|
|
672
|
+
[[package]]
|
|
673
|
+
name = "winapi"
|
|
674
|
+
version = "0.3.9"
|
|
675
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
676
|
+
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
|
677
|
+
dependencies = [
|
|
678
|
+
"winapi-i686-pc-windows-gnu",
|
|
679
|
+
"winapi-x86_64-pc-windows-gnu",
|
|
680
|
+
]
|
|
681
|
+
|
|
682
|
+
[[package]]
|
|
683
|
+
name = "winapi-i686-pc-windows-gnu"
|
|
684
|
+
version = "0.4.0"
|
|
685
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
686
|
+
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
|
687
|
+
|
|
688
|
+
[[package]]
|
|
689
|
+
name = "winapi-x86_64-pc-windows-gnu"
|
|
690
|
+
version = "0.4.0"
|
|
691
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
692
|
+
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
693
|
+
|
|
661
694
|
[[package]]
|
|
662
695
|
name = "windows-core"
|
|
663
696
|
version = "0.62.2"
|
package/Cargo.toml
CHANGED
|
@@ -44,6 +44,9 @@ pub fn cmd_init(args: &InitArgs) -> Result<CmdResult, CliError> {
|
|
|
44
44
|
let team_root = args.workspace.join(".team");
|
|
45
45
|
let spec_path = team_root.join("current").join("team.spec.yaml");
|
|
46
46
|
let state_path = args.workspace.join("team_state.md");
|
|
47
|
+
let team_md_path = args.workspace.join("TEAM.md");
|
|
48
|
+
let agents_dir = args.workspace.join("agents");
|
|
49
|
+
let default_agent_path = agents_dir.join("worker.md");
|
|
47
50
|
if spec_path.exists() && !args.force {
|
|
48
51
|
return Err(CliError::Runtime(format!(
|
|
49
52
|
"{} already exists; pass --force to overwrite",
|
|
@@ -57,10 +60,23 @@ pub fn cmd_init(args: &InitArgs) -> Result<CmdResult, CliError> {
|
|
|
57
60
|
team_root.join("logs"),
|
|
58
61
|
team_root.join("messages"),
|
|
59
62
|
team_root.join("artifacts"),
|
|
63
|
+
agents_dir.clone(),
|
|
60
64
|
] {
|
|
61
65
|
std::fs::create_dir_all(&dir)?;
|
|
62
66
|
}
|
|
63
67
|
std::fs::write(&spec_path, INIT_SPEC_TEMPLATE)?;
|
|
68
|
+
if args.force || !team_md_path.exists() {
|
|
69
|
+
std::fs::write(
|
|
70
|
+
&team_md_path,
|
|
71
|
+
"---\nname: current\nobjective: Pending.\nprovider: fake\n---\n\nPending.\n",
|
|
72
|
+
)?;
|
|
73
|
+
}
|
|
74
|
+
if args.force || !default_agent_path.exists() {
|
|
75
|
+
std::fs::write(
|
|
76
|
+
&default_agent_path,
|
|
77
|
+
"---\nname: worker\nrole: Worker\nprovider: fake\ntools:\n - mcp_team\n---\n\nWait for instructions.\n",
|
|
78
|
+
)?;
|
|
79
|
+
}
|
|
64
80
|
if args.force || !state_path.exists() {
|
|
65
81
|
std::fs::write(&state_path, INIT_STATE_TEMPLATE)?;
|
|
66
82
|
}
|
|
@@ -92,8 +108,30 @@ pub fn cmd_quick_start(args: &QuickStartArgs) -> Result<CmdResult, CliError> {
|
|
|
92
108
|
args.yes,
|
|
93
109
|
args.fresh,
|
|
94
110
|
)?;
|
|
95
|
-
|
|
96
|
-
|
|
111
|
+
let readiness = value.get("readiness").and_then(Value::as_object);
|
|
112
|
+
let all_resumable_have_session = readiness
|
|
113
|
+
.and_then(|readiness| readiness.get("all_resumable_have_session"))
|
|
114
|
+
.and_then(Value::as_bool)
|
|
115
|
+
.unwrap_or(true);
|
|
116
|
+
let session_capture_incomplete = readiness
|
|
117
|
+
.and_then(|readiness| readiness.get("session_capture_incomplete"))
|
|
118
|
+
.and_then(Value::as_bool)
|
|
119
|
+
.unwrap_or(!all_resumable_have_session);
|
|
120
|
+
let readiness_ready = readiness
|
|
121
|
+
.and_then(|readiness| readiness.get("ready"))
|
|
122
|
+
.and_then(Value::as_bool)
|
|
123
|
+
.unwrap_or(true);
|
|
124
|
+
let status = value.get("status").and_then(Value::as_str).map(str::to_string);
|
|
125
|
+
if args.json
|
|
126
|
+
|| value.get("ok").and_then(Value::as_bool) == Some(false)
|
|
127
|
+
|| session_capture_incomplete
|
|
128
|
+
|| !readiness_ready
|
|
129
|
+
{
|
|
130
|
+
let mut result = CmdResult::from_json(value, args.json);
|
|
131
|
+
if args.json && status.as_deref() == Some("pending_tool_load") {
|
|
132
|
+
result.exit = ExitCode::Ok;
|
|
133
|
+
}
|
|
134
|
+
Ok(result)
|
|
97
135
|
} else {
|
|
98
136
|
Ok(CmdResult::human(
|
|
99
137
|
value
|
|
@@ -360,7 +398,18 @@ pub fn cmd_settle(args: &SettleArgs) -> Result<CmdResult, CliError> {
|
|
|
360
398
|
}
|
|
361
399
|
|
|
362
400
|
fn settle_value(workspace: &Path) -> Result<Value, CliError> {
|
|
363
|
-
let
|
|
401
|
+
let selected = crate::state::selector::resolve_active_team(
|
|
402
|
+
workspace,
|
|
403
|
+
None,
|
|
404
|
+
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
405
|
+
)
|
|
406
|
+
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
407
|
+
let mut collect = messaging::collect_for_team(
|
|
408
|
+
&selected.run_workspace,
|
|
409
|
+
None,
|
|
410
|
+
false,
|
|
411
|
+
Some(&selected.team_key),
|
|
412
|
+
)?;
|
|
364
413
|
if collect.get("ok").and_then(Value::as_bool) == Some(false) {
|
|
365
414
|
let message = collect
|
|
366
415
|
.get("error")
|
|
@@ -369,7 +418,7 @@ fn settle_value(workspace: &Path) -> Result<Value, CliError> {
|
|
|
369
418
|
return Err(CliError::Runtime(message.to_string()));
|
|
370
419
|
}
|
|
371
420
|
let coordinator_log = crate::coordinator::coordinator_log_path(
|
|
372
|
-
&crate::coordinator::WorkspacePath::new(
|
|
421
|
+
&crate::coordinator::WorkspacePath::new(selected.run_workspace.clone()),
|
|
373
422
|
);
|
|
374
423
|
let collect_object = collect
|
|
375
424
|
.as_object_mut()
|
|
@@ -382,21 +431,92 @@ fn settle_value(workspace: &Path) -> Result<Value, CliError> {
|
|
|
382
431
|
"log": coordinator_log.to_string_lossy().to_string(),
|
|
383
432
|
}),
|
|
384
433
|
);
|
|
385
|
-
|
|
386
|
-
|
|
434
|
+
collect_object.insert("team_key".to_string(), json!(selected.team_key.clone()));
|
|
435
|
+
collect_object.insert("active_team_key".to_string(), json!(selected.team_key.clone()));
|
|
436
|
+
collect_object.insert("team".to_string(), json!(selected.team_key.clone()));
|
|
437
|
+
if let Some(collected_results) = collect_object.get("collected_results").cloned() {
|
|
438
|
+
collect_object.insert("collected".to_string(), collected_results);
|
|
439
|
+
}
|
|
440
|
+
let status_state =
|
|
441
|
+
crate::state::projection::select_runtime_state(&selected.run_workspace, Some(&selected.team_key))?;
|
|
442
|
+
let state_file = match (selected.spec_path.as_ref(), selected.spec_workspace.as_ref()) {
|
|
443
|
+
(Some(spec_path), Some(spec_workspace)) => match load_team_spec_at(spec_path)? {
|
|
444
|
+
Some(spec) => crate::lifecycle::restart::write_team_state(spec_workspace, &spec, &status_state)
|
|
445
|
+
.map_err(|e| CliError::Runtime(e.to_string()))?
|
|
446
|
+
.to_string_lossy()
|
|
447
|
+
.to_string(),
|
|
448
|
+
None => collect
|
|
449
|
+
.get("state_file")
|
|
450
|
+
.and_then(Value::as_str)
|
|
451
|
+
.unwrap_or("")
|
|
452
|
+
.to_string(),
|
|
453
|
+
},
|
|
454
|
+
_ => collect
|
|
455
|
+
.get("state_file")
|
|
456
|
+
.and_then(Value::as_str)
|
|
457
|
+
.unwrap_or("")
|
|
458
|
+
.to_string(),
|
|
459
|
+
};
|
|
460
|
+
if let Some(obj) = collect.as_object_mut() {
|
|
461
|
+
obj.insert("state_file".to_string(), json!(state_file.clone()));
|
|
462
|
+
}
|
|
463
|
+
let mut status = status_port::status_scoped(
|
|
464
|
+
&selected.run_workspace,
|
|
465
|
+
&status_state,
|
|
466
|
+
Some(&selected.team_key),
|
|
467
|
+
true,
|
|
468
|
+
false,
|
|
469
|
+
)?;
|
|
470
|
+
if let Some(obj) = status.as_object_mut() {
|
|
471
|
+
obj.insert("team_key".to_string(), json!(selected.team_key.clone()));
|
|
472
|
+
obj.insert("active_team_key".to_string(), json!(selected.team_key.clone()));
|
|
473
|
+
obj.insert("team".to_string(), json!(selected.team_key.clone()));
|
|
474
|
+
}
|
|
475
|
+
let details_log = write_settle_details_log(&selected.run_workspace, &collect, &status)?;
|
|
387
476
|
let collected_count = collect
|
|
388
477
|
.get("collected")
|
|
389
478
|
.and_then(Value::as_array)
|
|
390
479
|
.map_or(0, Vec::len);
|
|
480
|
+
let settled_results = settle_collected_results_for_team(
|
|
481
|
+
collect.get("collected_results"),
|
|
482
|
+
&selected.team_key,
|
|
483
|
+
);
|
|
391
484
|
Ok(json!({
|
|
392
485
|
"ok": true,
|
|
393
486
|
"summary": format!("collected {collected_count} result(s)"),
|
|
394
487
|
"next_actions": ["Review team_state.md and decide whether to continue or shutdown."],
|
|
395
488
|
"details_log": details_log.to_string_lossy().to_string(),
|
|
489
|
+
"collected_results": settled_results,
|
|
490
|
+
"collected": collect.get("collected").cloned().unwrap_or_else(|| json!([])),
|
|
491
|
+
"results": collect.get("results").cloned().unwrap_or_else(|| json!({})),
|
|
492
|
+
"state_file": state_file,
|
|
493
|
+
"status": status,
|
|
396
494
|
"collect": collect,
|
|
495
|
+
"team_key": selected.team_key,
|
|
496
|
+
"active_team_key": selected.team_key,
|
|
497
|
+
"team": selected.team_key,
|
|
498
|
+
"workspace": selected.run_workspace.to_string_lossy().to_string(),
|
|
397
499
|
}))
|
|
398
500
|
}
|
|
399
501
|
|
|
502
|
+
fn settle_collected_results_for_team(value: Option<&Value>, team_key: &str) -> Value {
|
|
503
|
+
let Some(Value::Array(results)) = value else {
|
|
504
|
+
return json!([]);
|
|
505
|
+
};
|
|
506
|
+
Value::Array(
|
|
507
|
+
results
|
|
508
|
+
.iter()
|
|
509
|
+
.map(|result| {
|
|
510
|
+
let mut result = result.clone();
|
|
511
|
+
if let Some(obj) = result.as_object_mut() {
|
|
512
|
+
obj.insert("owner_team_id".to_string(), json!(team_key));
|
|
513
|
+
}
|
|
514
|
+
result
|
|
515
|
+
})
|
|
516
|
+
.collect(),
|
|
517
|
+
)
|
|
518
|
+
}
|
|
519
|
+
|
|
400
520
|
fn write_settle_details_log(workspace: &Path, collect: &Value, status: &Value) -> Result<PathBuf, CliError> {
|
|
401
521
|
let logs = workspace.join(".team").join("logs");
|
|
402
522
|
std::fs::create_dir_all(&logs)?;
|
|
@@ -427,7 +547,13 @@ pub fn cmd_repair_state(args: &RepairStateArgs) -> Result<CmdResult, CliError> {
|
|
|
427
547
|
args.status
|
|
428
548
|
)));
|
|
429
549
|
}
|
|
430
|
-
let
|
|
550
|
+
let selected = crate::state::selector::resolve_active_team(
|
|
551
|
+
&args.workspace,
|
|
552
|
+
None,
|
|
553
|
+
crate::state::selector::SelectorMode::RequireSpec,
|
|
554
|
+
)
|
|
555
|
+
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
556
|
+
let mut state = selected.state;
|
|
431
557
|
let before = find_task_projection(&state, &args.task_id).unwrap_or_else(repair_task_projection_null);
|
|
432
558
|
update_task(
|
|
433
559
|
&mut state,
|
|
@@ -437,12 +563,20 @@ pub fn cmd_repair_state(args: &RepairStateArgs) -> Result<CmdResult, CliError> {
|
|
|
437
563
|
args.summary.as_deref(),
|
|
438
564
|
);
|
|
439
565
|
let after = find_task_projection(&state, &args.task_id).unwrap_or_else(repair_task_projection_null);
|
|
440
|
-
crate::state::
|
|
441
|
-
let
|
|
566
|
+
crate::state::projection::save_team_scoped_state(&selected.run_workspace, &state)?;
|
|
567
|
+
let spec_path = selected
|
|
568
|
+
.spec_path
|
|
569
|
+
.as_ref()
|
|
442
570
|
.ok_or_else(|| CliError::Runtime("team.spec.yaml not found".to_string()))?;
|
|
443
|
-
let
|
|
571
|
+
let spec = load_team_spec_at(spec_path)?
|
|
572
|
+
.ok_or_else(|| CliError::Runtime("team.spec.yaml not found".to_string()))?;
|
|
573
|
+
let spec_workspace = selected
|
|
574
|
+
.spec_workspace
|
|
575
|
+
.as_ref()
|
|
576
|
+
.ok_or_else(|| CliError::Runtime("active team spec workspace not found".to_string()))?;
|
|
577
|
+
let state_file = crate::lifecycle::restart::write_team_state(spec_workspace, &spec, &state)
|
|
444
578
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
445
|
-
crate::event_log::EventLog::new(&
|
|
579
|
+
crate::event_log::EventLog::new(&selected.run_workspace)
|
|
446
580
|
.write(
|
|
447
581
|
"repair_state.task",
|
|
448
582
|
json!({
|
|
@@ -466,9 +600,15 @@ pub fn cmd_repair_state(args: &RepairStateArgs) -> Result<CmdResult, CliError> {
|
|
|
466
600
|
|
|
467
601
|
/// `cmd_diagnose`(`parser.py:298`)。
|
|
468
602
|
pub fn cmd_diagnose(args: &DiagnoseArgs) -> Result<CmdResult, CliError> {
|
|
469
|
-
let
|
|
470
|
-
|
|
471
|
-
|
|
603
|
+
let selected = crate::state::selector::resolve_active_team(
|
|
604
|
+
&args.workspace,
|
|
605
|
+
None,
|
|
606
|
+
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
607
|
+
)
|
|
608
|
+
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
609
|
+
let state = selected.state;
|
|
610
|
+
let event_log = selected.run_workspace.join(".team").join("logs").join("events.jsonl");
|
|
611
|
+
let backend = crate::tmux_backend::TmuxBackend::for_workspace(&selected.run_workspace);
|
|
472
612
|
let (issues, suggested_repairs) = diagnose_runtime(&state, &backend);
|
|
473
613
|
let ok = issues.as_array().is_some_and(Vec::is_empty);
|
|
474
614
|
Ok(CmdResult::from_json(
|
|
@@ -479,6 +619,7 @@ pub fn cmd_diagnose(args: &DiagnoseArgs) -> Result<CmdResult, CliError> {
|
|
|
479
619
|
"providers": provider_doctor_checks(),
|
|
480
620
|
"runtime": {
|
|
481
621
|
"workspace": args.workspace.to_string_lossy().to_string(),
|
|
622
|
+
"team_key": selected.team_key,
|
|
482
623
|
"session_name": state.get("session_name").cloned().unwrap_or(Value::Null),
|
|
483
624
|
"leader_receiver": state.get("leader_receiver").cloned().unwrap_or(Value::Null),
|
|
484
625
|
"agent_count": state.get("agents").and_then(Value::as_object).map_or(0, serde_json::Map::len),
|
|
@@ -559,18 +700,33 @@ pub fn cmd_peek(args: &PeekArgs) -> Result<CmdResult, CliError> {
|
|
|
559
700
|
if !windows.iter().any(|w| w.as_str() == window) {
|
|
560
701
|
return Ok(peek_unavailable(&args.agent, args.json));
|
|
561
702
|
}
|
|
703
|
+
let range = args
|
|
704
|
+
.head
|
|
705
|
+
.map(|head| crate::transport::CaptureRange::Head(head as u32))
|
|
706
|
+
.unwrap_or_else(|| crate::transport::CaptureRange::Tail(args.tail as u32));
|
|
562
707
|
let capture = backend
|
|
563
708
|
.capture(
|
|
564
709
|
&crate::transport::Target::Pane(crate::transport::PaneId::new(target.clone())),
|
|
565
|
-
|
|
710
|
+
range,
|
|
566
711
|
)
|
|
567
712
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
713
|
+
let matches = args.search.as_ref().map(|needle| {
|
|
714
|
+
capture
|
|
715
|
+
.text
|
|
716
|
+
.lines()
|
|
717
|
+
.filter(|line| line.contains(needle))
|
|
718
|
+
.map(str::to_string)
|
|
719
|
+
.collect::<Vec<_>>()
|
|
720
|
+
});
|
|
568
721
|
Ok(CmdResult::from_json(
|
|
569
722
|
json!({
|
|
570
723
|
"ok": true,
|
|
571
724
|
"agent_id": args.agent,
|
|
572
725
|
"workspace": args.workspace.to_string_lossy().to_string(),
|
|
573
726
|
"tail": args.tail,
|
|
727
|
+
"head": args.head,
|
|
728
|
+
"search": args.search,
|
|
729
|
+
"matches": matches,
|
|
574
730
|
"pane_id": target,
|
|
575
731
|
"text": capture.text,
|
|
576
732
|
}),
|
|
@@ -634,6 +790,12 @@ fn run_fake_e2e(workspace: &Path) -> Result<Value, CliError> {
|
|
|
634
790
|
"reason": send.reason,
|
|
635
791
|
});
|
|
636
792
|
if send.ok {
|
|
793
|
+
if let Some(message_id) = send.message_id.as_deref() {
|
|
794
|
+
crate::message_store::MessageStore::open(workspace)
|
|
795
|
+
.map_err(crate::messaging::MessagingError::from)?
|
|
796
|
+
.mark(message_id, "delivered", None)
|
|
797
|
+
.map_err(crate::messaging::MessagingError::from)?;
|
|
798
|
+
}
|
|
637
799
|
let _ = messaging::report_result(
|
|
638
800
|
workspace,
|
|
639
801
|
&json!({
|
|
@@ -1083,6 +1245,16 @@ fn load_team_spec_optional(workspace: &Path, state: &Value) -> Result<Option<cra
|
|
|
1083
1245
|
.map_err(|e| CliError::Runtime(e.to_string()))
|
|
1084
1246
|
}
|
|
1085
1247
|
|
|
1248
|
+
fn load_team_spec_at(spec_path: &Path) -> Result<Option<crate::model::yaml::Value>, CliError> {
|
|
1249
|
+
if !spec_path.exists() {
|
|
1250
|
+
return Ok(None);
|
|
1251
|
+
}
|
|
1252
|
+
let text = std::fs::read_to_string(spec_path)?;
|
|
1253
|
+
crate::model::yaml::loads(&text)
|
|
1254
|
+
.map(Some)
|
|
1255
|
+
.map_err(|e| CliError::Runtime(e.to_string()))
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1086
1258
|
/// `cmd_approvals`(`commands.py:112`)。
|
|
1087
1259
|
pub fn cmd_approvals(args: &ApprovalsArgs) -> Result<CmdResult, CliError> {
|
|
1088
1260
|
let value = status_port::approvals(&args.workspace, args.agent.as_deref(), args.json)?;
|
|
@@ -1198,7 +1370,12 @@ pub fn cmd_shutdown(args: &ShutdownArgs) -> Result<CmdResult, CliError> {
|
|
|
1198
1370
|
/// `cmd_restart`(`commands.py:344`)。
|
|
1199
1371
|
pub fn cmd_restart(args: &RestartArgs) -> Result<CmdResult, CliError> {
|
|
1200
1372
|
Ok(CmdResult::from_json(
|
|
1201
|
-
lifecycle_port::restart(
|
|
1373
|
+
lifecycle_port::restart(
|
|
1374
|
+
&args.workspace,
|
|
1375
|
+
args.allow_fresh,
|
|
1376
|
+
args.team.as_deref(),
|
|
1377
|
+
args.session_converge_deadline_ms,
|
|
1378
|
+
)?,
|
|
1202
1379
|
args.json,
|
|
1203
1380
|
))
|
|
1204
1381
|
}
|
|
@@ -1314,7 +1491,7 @@ pub fn cmd_doctor(args: &DoctorArgs) -> Result<CmdResult, CliError> {
|
|
|
1314
1491
|
return Err(CliError::Runtime("--fix requires --gate".to_string()));
|
|
1315
1492
|
}
|
|
1316
1493
|
if args.comms || matches!(args.gate, Some(DoctorGate::Comms)) {
|
|
1317
|
-
let value =
|
|
1494
|
+
let value = crate::diagnose::comms::doctor_comms_json(&args.workspace, args.team.as_deref(), Some("comms"))?;
|
|
1318
1495
|
if !args.json {
|
|
1319
1496
|
let json_tail = serde_json::to_string_pretty(&sort_json(&value))?;
|
|
1320
1497
|
return Ok(CmdResult::human(format!("{COMMS_BOUNDARY_TEXT}\n{json_tail}")));
|
|
@@ -1322,9 +1499,9 @@ pub fn cmd_doctor(args: &DoctorArgs) -> Result<CmdResult, CliError> {
|
|
|
1322
1499
|
return Ok(CmdResult::from_json(value, true));
|
|
1323
1500
|
}
|
|
1324
1501
|
let value = if matches!(args.gate, Some(DoctorGate::Orphans)) {
|
|
1325
|
-
|
|
1502
|
+
crate::diagnose::orphans::orphan_gate_json(args.fix, args.confirm)?
|
|
1326
1503
|
} else if args.cleanup_orphans {
|
|
1327
|
-
|
|
1504
|
+
crate::diagnose::orphans::cleanup_orphans_json(args.confirm)?
|
|
1328
1505
|
} else if args.fix_schema {
|
|
1329
1506
|
diagnose_port::fix_schema(&args.workspace)?
|
|
1330
1507
|
} else {
|
|
@@ -134,13 +134,18 @@ pub(crate) fn build_preflight_report(team: &std::path::Path) -> Result<Value, Cl
|
|
|
134
134
|
|| team.join("profiles").exists();
|
|
135
135
|
let profile_dir_check = json!({
|
|
136
136
|
"name": "profile_dir",
|
|
137
|
-
"ok": profile_dir_exists,
|
|
138
|
-
});
|
|
139
|
-
let profile_smoke_check = json!({
|
|
140
|
-
"name": "profile_smoke",
|
|
141
137
|
"ok": true,
|
|
142
|
-
"status": if profile_dir_exists { "
|
|
138
|
+
"status": if profile_dir_exists { "present" } else { "not_required" },
|
|
143
139
|
});
|
|
140
|
+
let profile_smoke_check = build_profile_smoke_check_for_team(team)?;
|
|
141
|
+
if profile_smoke_check.get("ok").and_then(Value::as_bool) == Some(false) {
|
|
142
|
+
let reason = profile_smoke_check
|
|
143
|
+
.get("reason")
|
|
144
|
+
.or_else(|| profile_smoke_check.get("error"))
|
|
145
|
+
.and_then(Value::as_str)
|
|
146
|
+
.unwrap_or("profile smoke failed");
|
|
147
|
+
next_actions.push(json!(format!("fix compatible_api profile smoke: {reason}")));
|
|
148
|
+
}
|
|
144
149
|
checks.push(json!({
|
|
145
150
|
"name": "profiles",
|
|
146
151
|
"ok": true,
|
|
@@ -197,14 +202,110 @@ pub(crate) fn build_preflight_report(team: &std::path::Path) -> Result<Value, Cl
|
|
|
197
202
|
Ok(report)
|
|
198
203
|
}
|
|
199
204
|
|
|
205
|
+
pub(crate) fn build_profile_smoke_check_for_team(team: &std::path::Path) -> Result<Value, CliError> {
|
|
206
|
+
let workspace = crate::model::paths::team_workspace(team)
|
|
207
|
+
.map_err(|error| CliError::Runtime(error.to_string()))?;
|
|
208
|
+
let spec = match crate::compiler::compile_team(team) {
|
|
209
|
+
Ok(spec) => spec,
|
|
210
|
+
Err(error) => {
|
|
211
|
+
return Ok(json!({
|
|
212
|
+
"name": "profile_smoke",
|
|
213
|
+
"ok": false,
|
|
214
|
+
"status": "profile_invalid",
|
|
215
|
+
"reason": error.to_string(),
|
|
216
|
+
"secret_values_printed": false,
|
|
217
|
+
"checks": [],
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
let agents = spec
|
|
222
|
+
.get("agents")
|
|
223
|
+
.and_then(crate::model::yaml::Value::as_list)
|
|
224
|
+
.unwrap_or(&[]);
|
|
225
|
+
let checks = crate::lifecycle::profile_smoke::profile_smoke_checks_for_agents_with_profile_dir(
|
|
226
|
+
&workspace,
|
|
227
|
+
agents,
|
|
228
|
+
Some(&team.join("profiles")),
|
|
229
|
+
crate::lifecycle::profile_smoke::DEFAULT_PROFILE_SMOKE_TIMEOUT,
|
|
230
|
+
);
|
|
231
|
+
Ok(aggregate_profile_smoke_checks(checks))
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fn aggregate_profile_smoke_checks(checks: Vec<Value>) -> Value {
|
|
235
|
+
let failed = checks
|
|
236
|
+
.iter()
|
|
237
|
+
.filter(|check| check.get("ok").and_then(Value::as_bool) == Some(false))
|
|
238
|
+
.cloned()
|
|
239
|
+
.collect::<Vec<_>>();
|
|
240
|
+
let ok = failed.is_empty();
|
|
241
|
+
let status = if !ok {
|
|
242
|
+
failed
|
|
243
|
+
.first()
|
|
244
|
+
.and_then(|check| check.get("status").and_then(Value::as_str))
|
|
245
|
+
.unwrap_or("smoke_failed")
|
|
246
|
+
} else if checks
|
|
247
|
+
.iter()
|
|
248
|
+
.any(|check| check.get("status").and_then(Value::as_str) == Some("smoke_passed"))
|
|
249
|
+
{
|
|
250
|
+
"smoke_passed"
|
|
251
|
+
} else if checks
|
|
252
|
+
.iter()
|
|
253
|
+
.any(|check| check.get("status").and_then(Value::as_str) == Some("skipped_by_profile"))
|
|
254
|
+
{
|
|
255
|
+
"skipped_by_profile"
|
|
256
|
+
} else {
|
|
257
|
+
"not_required"
|
|
258
|
+
};
|
|
259
|
+
let mut out = json!({
|
|
260
|
+
"name": "profile_smoke",
|
|
261
|
+
"ok": ok,
|
|
262
|
+
"status": status,
|
|
263
|
+
"checks": checks,
|
|
264
|
+
"secret_values_printed": false,
|
|
265
|
+
});
|
|
266
|
+
if let Some(first) = failed.first() {
|
|
267
|
+
copy_optional_field(first, &mut out, "reason");
|
|
268
|
+
copy_optional_field(first, &mut out, "http_status");
|
|
269
|
+
copy_optional_field(first, &mut out, "endpoint");
|
|
270
|
+
copy_optional_field(first, &mut out, "error");
|
|
271
|
+
} else if let Some(first_passed) = checks
|
|
272
|
+
.iter()
|
|
273
|
+
.find(|check| check.get("status").and_then(Value::as_str) == Some("smoke_passed"))
|
|
274
|
+
{
|
|
275
|
+
copy_optional_field(first_passed, &mut out, "http_status");
|
|
276
|
+
copy_optional_field(first_passed, &mut out, "endpoint");
|
|
277
|
+
}
|
|
278
|
+
out
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
fn copy_optional_field(from: &Value, to: &mut Value, key: &str) {
|
|
282
|
+
let Some(value) = from.get(key).cloned() else {
|
|
283
|
+
return;
|
|
284
|
+
};
|
|
285
|
+
let Some(obj) = to.as_object_mut() else {
|
|
286
|
+
return;
|
|
287
|
+
};
|
|
288
|
+
obj.insert(key.to_string(), value);
|
|
289
|
+
}
|
|
290
|
+
|
|
200
291
|
pub(crate) fn build_wait_ready_report(workspace: &std::path::Path, timeout: f64) -> Result<Value, CliError> {
|
|
292
|
+
let selected = crate::state::selector::resolve_active_team(
|
|
293
|
+
workspace,
|
|
294
|
+
None,
|
|
295
|
+
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
296
|
+
)
|
|
297
|
+
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
201
298
|
let timeout = if timeout.is_finite() && timeout > 0.0 { timeout } else { 0.0 };
|
|
202
299
|
let deadline = std::time::Instant::now() + std::time::Duration::from_secs_f64(timeout);
|
|
203
300
|
let mut readiness;
|
|
204
301
|
loop {
|
|
205
|
-
let mut state = crate::state::
|
|
206
|
-
|
|
207
|
-
|
|
302
|
+
let mut state = crate::state::projection::select_runtime_state(
|
|
303
|
+
&selected.run_workspace,
|
|
304
|
+
Some(&selected.team_key),
|
|
305
|
+
)
|
|
306
|
+
.unwrap_or_else(|_| selected.state.clone());
|
|
307
|
+
inject_tmux_session_present(&selected.run_workspace, &mut state);
|
|
308
|
+
inject_message_counts(&selected.run_workspace, &mut state)?;
|
|
208
309
|
readiness = wait_readiness(&state);
|
|
209
310
|
let awaiting_trust = readiness
|
|
210
311
|
.get("awaiting_trust_prompt")
|
|
@@ -231,6 +332,14 @@ pub(crate) fn build_wait_ready_report(workspace: &std::path::Path, timeout: f64)
|
|
|
231
332
|
)
|
|
232
333
|
} else if ready {
|
|
233
334
|
(true, "ready", "ready", "workers ready", Vec::new())
|
|
335
|
+
} else if readiness.get("session_capture_complete").and_then(Value::as_bool) == Some(false) {
|
|
336
|
+
(
|
|
337
|
+
false,
|
|
338
|
+
"pending",
|
|
339
|
+
"session_capture_incomplete",
|
|
340
|
+
"provider session capture is incomplete",
|
|
341
|
+
vec![json!("wait for provider session capture before restart")],
|
|
342
|
+
)
|
|
234
343
|
} else {
|
|
235
344
|
(
|
|
236
345
|
false,
|
|
@@ -241,7 +350,7 @@ pub(crate) fn build_wait_ready_report(workspace: &std::path::Path, timeout: f64)
|
|
|
241
350
|
)
|
|
242
351
|
};
|
|
243
352
|
let details_log = write_details_log(
|
|
244
|
-
|
|
353
|
+
&selected.run_workspace,
|
|
245
354
|
"wait-ready",
|
|
246
355
|
&json!({
|
|
247
356
|
"ok": ok,
|
|
@@ -282,6 +391,20 @@ pub(crate) fn wait_readiness(state: &Value) -> Value {
|
|
|
282
391
|
let mut mcp_ready = false;
|
|
283
392
|
let mut task_prompt_delivered = false;
|
|
284
393
|
let mut awaiting_trust_prompt = false;
|
|
394
|
+
let mut incomplete_sessions = Vec::new();
|
|
395
|
+
let all_attached_receiver = state
|
|
396
|
+
.get("leader_receiver")
|
|
397
|
+
.and_then(Value::as_object)
|
|
398
|
+
.is_none_or(|receiver| {
|
|
399
|
+
receiver
|
|
400
|
+
.get("status")
|
|
401
|
+
.and_then(Value::as_str)
|
|
402
|
+
== Some("attached")
|
|
403
|
+
|| receiver
|
|
404
|
+
.get("pane_id")
|
|
405
|
+
.and_then(Value::as_str)
|
|
406
|
+
.is_some_and(|pane| !pane.is_empty() && pane != "__team_agent_unbound__")
|
|
407
|
+
});
|
|
285
408
|
|
|
286
409
|
if let Some(agents) = agents {
|
|
287
410
|
process_started = state
|
|
@@ -314,14 +437,25 @@ pub(crate) fn wait_readiness(state: &Value) -> Value {
|
|
|
314
437
|
.and_then(Value::as_str)
|
|
315
438
|
== Some("awaiting_trust_prompt")
|
|
316
439
|
});
|
|
440
|
+
incomplete_sessions = crate::session_capture::incomplete_interacted_resumable_agent_ids(state);
|
|
317
441
|
}
|
|
318
|
-
let
|
|
442
|
+
let all_resumable_have_session = incomplete_sessions.is_empty();
|
|
443
|
+
let session_capture_incomplete = !all_resumable_have_session;
|
|
444
|
+
let all_spawned = process_started && cli_prompt_ready && mcp_ready;
|
|
445
|
+
let ready = all_spawned && all_attached_receiver && all_resumable_have_session;
|
|
319
446
|
json!({
|
|
447
|
+
"all_attached_receiver": all_attached_receiver,
|
|
448
|
+
"all_resumable_have_session": all_resumable_have_session,
|
|
449
|
+
"all_spawned": all_spawned,
|
|
320
450
|
"awaiting_trust_prompt": awaiting_trust_prompt,
|
|
321
451
|
"cli_prompt_ready": cli_prompt_ready,
|
|
452
|
+
"incomplete_session_capture_agents": incomplete_sessions.clone(),
|
|
322
453
|
"mcp_ready": mcp_ready,
|
|
323
454
|
"process_started": process_started,
|
|
324
455
|
"ready": ready,
|
|
456
|
+
"session_capture_complete": all_resumable_have_session,
|
|
457
|
+
"session_capture_incomplete": session_capture_incomplete,
|
|
458
|
+
"pending_session_agent_ids": incomplete_sessions,
|
|
325
459
|
"task_prompt_delivered": task_prompt_delivered,
|
|
326
460
|
})
|
|
327
461
|
}
|