@team-agent/installer 0.3.1 → 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 +234 -26
- package/crates/team-agent/src/cli/diagnose.rs +144 -10
- package/crates/team-agent/src/cli/emit.rs +289 -54
- package/crates/team-agent/src/cli/leader.rs +37 -8
- package/crates/team-agent/src/cli/mod.rs +1281 -196
- package/crates/team-agent/src/cli/status_port.rs +195 -46
- 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 +59 -3
- package/crates/team-agent/src/cli/types.rs +18 -0
- package/crates/team-agent/src/compiler.rs +15 -5
- package/crates/team-agent/src/coordinator/health.rs +95 -17
- 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 +1061 -146
- 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 +183 -24
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +498 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +27 -7
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +19 -0
- package/crates/team-agent/src/lifecycle/restart.rs +24 -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 +470 -59
- package/crates/team-agent/src/messaging/mod.rs +9 -6
- package/crates/team-agent/src/messaging/results.rs +353 -63
- 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 +564 -63
- 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/helpers.rs +10 -1
- 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 +170 -1
- package/crates/team-agent/src/state/projection.rs +141 -8
- package/crates/team-agent/src/state/selector.rs +5 -2
- package/crates/team-agent/src/tmux_backend.rs +161 -64
- 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
|
}
|
|
@@ -85,15 +101,37 @@ pub fn cmd_init(args: &InitArgs) -> Result<CmdResult, CliError> {
|
|
|
85
101
|
/// `cmd_quick_start`(`commands.py:18`)。`--json` 或 `!ok` → 整 dict;否则 `result["summary"]`。
|
|
86
102
|
pub fn cmd_quick_start(args: &QuickStartArgs) -> Result<CmdResult, CliError> {
|
|
87
103
|
let value = lifecycle_port::quick_start(
|
|
88
|
-
&args.
|
|
104
|
+
&args.workspace,
|
|
89
105
|
&args.agents_dir,
|
|
90
106
|
args.name.as_deref(),
|
|
91
107
|
args.team_id.as_deref(),
|
|
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
|
|
@@ -123,6 +161,10 @@ pub fn cmd_compile(args: &CompileArgs) -> Result<CmdResult, CliError> {
|
|
|
123
161
|
/// `cmd_status`(`commands.py:90`)。三态:`--summary`(xor json,xor agent)→五行文本;
|
|
124
162
|
/// `--json`→`status_port::status(compact=!detail)`;else→`status_port::format_status(agent)`。
|
|
125
163
|
pub fn cmd_status(args: &StatusArgs) -> Result<CmdResult, CliError> {
|
|
164
|
+
cmd_status_for_team(args, None)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
pub fn cmd_status_for_team(args: &StatusArgs, team: Option<&str>) -> Result<CmdResult, CliError> {
|
|
126
168
|
if args.summary && args.json {
|
|
127
169
|
return Err(CliError::Runtime(
|
|
128
170
|
"--summary and --json are mutually exclusive".to_string(),
|
|
@@ -135,7 +177,7 @@ pub fn cmd_status(args: &StatusArgs) -> Result<CmdResult, CliError> {
|
|
|
135
177
|
}
|
|
136
178
|
let selected = match crate::state::selector::resolve_active_team(
|
|
137
179
|
&args.workspace,
|
|
138
|
-
|
|
180
|
+
team,
|
|
139
181
|
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
140
182
|
) {
|
|
141
183
|
Ok(selected) => selected,
|
|
@@ -147,15 +189,29 @@ pub fn cmd_status(args: &StatusArgs) -> Result<CmdResult, CliError> {
|
|
|
147
189
|
}
|
|
148
190
|
};
|
|
149
191
|
if args.summary {
|
|
150
|
-
let value = status_port::
|
|
192
|
+
let value = status_port::status_scoped(
|
|
193
|
+
&selected.run_workspace,
|
|
194
|
+
&selected.state,
|
|
195
|
+
Some(&selected.team_key),
|
|
196
|
+
true,
|
|
197
|
+
false,
|
|
198
|
+
)?;
|
|
151
199
|
return Ok(CmdResult::human(format_status_summary(&value)));
|
|
152
200
|
}
|
|
153
201
|
if args.json {
|
|
154
|
-
let value = status_port::
|
|
202
|
+
let value = status_port::status_scoped(
|
|
203
|
+
&selected.run_workspace,
|
|
204
|
+
&selected.state,
|
|
205
|
+
Some(&selected.team_key),
|
|
206
|
+
status_compact_flag(args.detail),
|
|
207
|
+
args.detail,
|
|
208
|
+
)?;
|
|
155
209
|
return Ok(CmdResult::from_json(value, true));
|
|
156
210
|
}
|
|
157
|
-
Ok(CmdResult::human(status_port::
|
|
211
|
+
Ok(CmdResult::human(status_port::format_status_scoped(
|
|
158
212
|
&selected.run_workspace,
|
|
213
|
+
&selected.state,
|
|
214
|
+
Some(&selected.team_key),
|
|
159
215
|
args.agent.as_deref(),
|
|
160
216
|
)?))
|
|
161
217
|
}
|
|
@@ -252,9 +308,13 @@ pub fn cmd_validate_result(args: &ValidateResultArgs) -> Result<CmdResult, CliEr
|
|
|
252
308
|
|
|
253
309
|
/// `cmd_collect`(`parser.py:292`)。
|
|
254
310
|
pub fn cmd_collect(args: &CollectArgs) -> Result<CmdResult, CliError> {
|
|
311
|
+
cmd_collect_for_team(args, None)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
pub fn cmd_collect_for_team(args: &CollectArgs, team: Option<&str>) -> Result<CmdResult, CliError> {
|
|
255
315
|
let selected = match crate::state::selector::resolve_active_team(
|
|
256
316
|
&args.workspace,
|
|
257
|
-
|
|
317
|
+
team,
|
|
258
318
|
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
259
319
|
) {
|
|
260
320
|
Ok(selected) => selected,
|
|
@@ -269,7 +329,16 @@ pub fn cmd_collect(args: &CollectArgs) -> Result<CmdResult, CliError> {
|
|
|
269
329
|
));
|
|
270
330
|
}
|
|
271
331
|
};
|
|
272
|
-
let value = match
|
|
332
|
+
let value = match if team.is_some() {
|
|
333
|
+
messaging::collect_for_team(
|
|
334
|
+
&selected.run_workspace,
|
|
335
|
+
args.result_file.as_deref(),
|
|
336
|
+
false,
|
|
337
|
+
Some(&selected.team_key),
|
|
338
|
+
)
|
|
339
|
+
} else {
|
|
340
|
+
messaging::collect(&selected.run_workspace, args.result_file.as_deref(), false)
|
|
341
|
+
} {
|
|
273
342
|
Ok(value) => value,
|
|
274
343
|
Err(error) => {
|
|
275
344
|
return Ok(CmdResult::from_json(
|
|
@@ -329,7 +398,18 @@ pub fn cmd_settle(args: &SettleArgs) -> Result<CmdResult, CliError> {
|
|
|
329
398
|
}
|
|
330
399
|
|
|
331
400
|
fn settle_value(workspace: &Path) -> Result<Value, CliError> {
|
|
332
|
-
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
|
+
)?;
|
|
333
413
|
if collect.get("ok").and_then(Value::as_bool) == Some(false) {
|
|
334
414
|
let message = collect
|
|
335
415
|
.get("error")
|
|
@@ -338,7 +418,7 @@ fn settle_value(workspace: &Path) -> Result<Value, CliError> {
|
|
|
338
418
|
return Err(CliError::Runtime(message.to_string()));
|
|
339
419
|
}
|
|
340
420
|
let coordinator_log = crate::coordinator::coordinator_log_path(
|
|
341
|
-
&crate::coordinator::WorkspacePath::new(
|
|
421
|
+
&crate::coordinator::WorkspacePath::new(selected.run_workspace.clone()),
|
|
342
422
|
);
|
|
343
423
|
let collect_object = collect
|
|
344
424
|
.as_object_mut()
|
|
@@ -351,21 +431,92 @@ fn settle_value(workspace: &Path) -> Result<Value, CliError> {
|
|
|
351
431
|
"log": coordinator_log.to_string_lossy().to_string(),
|
|
352
432
|
}),
|
|
353
433
|
);
|
|
354
|
-
|
|
355
|
-
|
|
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)?;
|
|
356
476
|
let collected_count = collect
|
|
357
477
|
.get("collected")
|
|
358
478
|
.and_then(Value::as_array)
|
|
359
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
|
+
);
|
|
360
484
|
Ok(json!({
|
|
361
485
|
"ok": true,
|
|
362
486
|
"summary": format!("collected {collected_count} result(s)"),
|
|
363
487
|
"next_actions": ["Review team_state.md and decide whether to continue or shutdown."],
|
|
364
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,
|
|
365
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(),
|
|
366
499
|
}))
|
|
367
500
|
}
|
|
368
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
|
+
|
|
369
520
|
fn write_settle_details_log(workspace: &Path, collect: &Value, status: &Value) -> Result<PathBuf, CliError> {
|
|
370
521
|
let logs = workspace.join(".team").join("logs");
|
|
371
522
|
std::fs::create_dir_all(&logs)?;
|
|
@@ -396,7 +547,13 @@ pub fn cmd_repair_state(args: &RepairStateArgs) -> Result<CmdResult, CliError> {
|
|
|
396
547
|
args.status
|
|
397
548
|
)));
|
|
398
549
|
}
|
|
399
|
-
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;
|
|
400
557
|
let before = find_task_projection(&state, &args.task_id).unwrap_or_else(repair_task_projection_null);
|
|
401
558
|
update_task(
|
|
402
559
|
&mut state,
|
|
@@ -406,12 +563,20 @@ pub fn cmd_repair_state(args: &RepairStateArgs) -> Result<CmdResult, CliError> {
|
|
|
406
563
|
args.summary.as_deref(),
|
|
407
564
|
);
|
|
408
565
|
let after = find_task_projection(&state, &args.task_id).unwrap_or_else(repair_task_projection_null);
|
|
409
|
-
crate::state::
|
|
410
|
-
let
|
|
566
|
+
crate::state::projection::save_team_scoped_state(&selected.run_workspace, &state)?;
|
|
567
|
+
let spec_path = selected
|
|
568
|
+
.spec_path
|
|
569
|
+
.as_ref()
|
|
570
|
+
.ok_or_else(|| CliError::Runtime("team.spec.yaml not found".to_string()))?;
|
|
571
|
+
let spec = load_team_spec_at(spec_path)?
|
|
411
572
|
.ok_or_else(|| CliError::Runtime("team.spec.yaml not found".to_string()))?;
|
|
412
|
-
let
|
|
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)
|
|
413
578
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
414
|
-
crate::event_log::EventLog::new(&
|
|
579
|
+
crate::event_log::EventLog::new(&selected.run_workspace)
|
|
415
580
|
.write(
|
|
416
581
|
"repair_state.task",
|
|
417
582
|
json!({
|
|
@@ -435,9 +600,15 @@ pub fn cmd_repair_state(args: &RepairStateArgs) -> Result<CmdResult, CliError> {
|
|
|
435
600
|
|
|
436
601
|
/// `cmd_diagnose`(`parser.py:298`)。
|
|
437
602
|
pub fn cmd_diagnose(args: &DiagnoseArgs) -> Result<CmdResult, CliError> {
|
|
438
|
-
let
|
|
439
|
-
|
|
440
|
-
|
|
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);
|
|
441
612
|
let (issues, suggested_repairs) = diagnose_runtime(&state, &backend);
|
|
442
613
|
let ok = issues.as_array().is_some_and(Vec::is_empty);
|
|
443
614
|
Ok(CmdResult::from_json(
|
|
@@ -448,6 +619,7 @@ pub fn cmd_diagnose(args: &DiagnoseArgs) -> Result<CmdResult, CliError> {
|
|
|
448
619
|
"providers": provider_doctor_checks(),
|
|
449
620
|
"runtime": {
|
|
450
621
|
"workspace": args.workspace.to_string_lossy().to_string(),
|
|
622
|
+
"team_key": selected.team_key,
|
|
451
623
|
"session_name": state.get("session_name").cloned().unwrap_or(Value::Null),
|
|
452
624
|
"leader_receiver": state.get("leader_receiver").cloned().unwrap_or(Value::Null),
|
|
453
625
|
"agent_count": state.get("agents").and_then(Value::as_object).map_or(0, serde_json::Map::len),
|
|
@@ -528,18 +700,33 @@ pub fn cmd_peek(args: &PeekArgs) -> Result<CmdResult, CliError> {
|
|
|
528
700
|
if !windows.iter().any(|w| w.as_str() == window) {
|
|
529
701
|
return Ok(peek_unavailable(&args.agent, args.json));
|
|
530
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));
|
|
531
707
|
let capture = backend
|
|
532
708
|
.capture(
|
|
533
709
|
&crate::transport::Target::Pane(crate::transport::PaneId::new(target.clone())),
|
|
534
|
-
|
|
710
|
+
range,
|
|
535
711
|
)
|
|
536
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
|
+
});
|
|
537
721
|
Ok(CmdResult::from_json(
|
|
538
722
|
json!({
|
|
539
723
|
"ok": true,
|
|
540
724
|
"agent_id": args.agent,
|
|
541
725
|
"workspace": args.workspace.to_string_lossy().to_string(),
|
|
542
726
|
"tail": args.tail,
|
|
727
|
+
"head": args.head,
|
|
728
|
+
"search": args.search,
|
|
729
|
+
"matches": matches,
|
|
543
730
|
"pane_id": target,
|
|
544
731
|
"text": capture.text,
|
|
545
732
|
}),
|
|
@@ -603,6 +790,12 @@ fn run_fake_e2e(workspace: &Path) -> Result<Value, CliError> {
|
|
|
603
790
|
"reason": send.reason,
|
|
604
791
|
});
|
|
605
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
|
+
}
|
|
606
799
|
let _ = messaging::report_result(
|
|
607
800
|
workspace,
|
|
608
801
|
&json!({
|
|
@@ -1052,6 +1245,16 @@ fn load_team_spec_optional(workspace: &Path, state: &Value) -> Result<Option<cra
|
|
|
1052
1245
|
.map_err(|e| CliError::Runtime(e.to_string()))
|
|
1053
1246
|
}
|
|
1054
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
|
+
|
|
1055
1258
|
/// `cmd_approvals`(`commands.py:112`)。
|
|
1056
1259
|
pub fn cmd_approvals(args: &ApprovalsArgs) -> Result<CmdResult, CliError> {
|
|
1057
1260
|
let value = status_port::approvals(&args.workspace, args.agent.as_deref(), args.json)?;
|
|
@@ -1167,7 +1370,12 @@ pub fn cmd_shutdown(args: &ShutdownArgs) -> Result<CmdResult, CliError> {
|
|
|
1167
1370
|
/// `cmd_restart`(`commands.py:344`)。
|
|
1168
1371
|
pub fn cmd_restart(args: &RestartArgs) -> Result<CmdResult, CliError> {
|
|
1169
1372
|
Ok(CmdResult::from_json(
|
|
1170
|
-
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
|
+
)?,
|
|
1171
1379
|
args.json,
|
|
1172
1380
|
))
|
|
1173
1381
|
}
|
|
@@ -1283,7 +1491,7 @@ pub fn cmd_doctor(args: &DoctorArgs) -> Result<CmdResult, CliError> {
|
|
|
1283
1491
|
return Err(CliError::Runtime("--fix requires --gate".to_string()));
|
|
1284
1492
|
}
|
|
1285
1493
|
if args.comms || matches!(args.gate, Some(DoctorGate::Comms)) {
|
|
1286
|
-
let value =
|
|
1494
|
+
let value = crate::diagnose::comms::doctor_comms_json(&args.workspace, args.team.as_deref(), Some("comms"))?;
|
|
1287
1495
|
if !args.json {
|
|
1288
1496
|
let json_tail = serde_json::to_string_pretty(&sort_json(&value))?;
|
|
1289
1497
|
return Ok(CmdResult::human(format!("{COMMS_BOUNDARY_TEXT}\n{json_tail}")));
|
|
@@ -1291,9 +1499,9 @@ pub fn cmd_doctor(args: &DoctorArgs) -> Result<CmdResult, CliError> {
|
|
|
1291
1499
|
return Ok(CmdResult::from_json(value, true));
|
|
1292
1500
|
}
|
|
1293
1501
|
let value = if matches!(args.gate, Some(DoctorGate::Orphans)) {
|
|
1294
|
-
|
|
1502
|
+
crate::diagnose::orphans::orphan_gate_json(args.fix, args.confirm)?
|
|
1295
1503
|
} else if args.cleanup_orphans {
|
|
1296
|
-
|
|
1504
|
+
crate::diagnose::orphans::cleanup_orphans_json(args.confirm)?
|
|
1297
1505
|
} else if args.fix_schema {
|
|
1298
1506
|
diagnose_port::fix_schema(&args.workspace)?
|
|
1299
1507
|
} else {
|