@team-agent/installer 0.4.1 → 0.4.2
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 +82 -22
- package/crates/team-agent/src/cli/diagnose.rs +6 -2
- package/crates/team-agent/src/cli/emit.rs +301 -38
- package/crates/team-agent/src/cli/mod.rs +7 -14
- package/crates/team-agent/src/cli/status_port.rs +12 -1
- package/crates/team-agent/src/cli/tests/base.rs +6 -2
- package/crates/team-agent/src/cli/tests/lane_c.rs +1 -1
- package/crates/team-agent/src/cli/tests/leader_watch.rs +5 -3
- package/crates/team-agent/src/cli/tests/main_preserved.rs +28 -2
- package/crates/team-agent/src/cli/tests/peer_allow.rs +19 -0
- package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +72 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -3
- package/crates/team-agent/src/cli/tests/status_send.rs +1 -0
- package/crates/team-agent/src/cli/types.rs +35 -2
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +7 -2
- package/crates/team-agent/src/diagnose/comms.rs +10 -2
- package/crates/team-agent/src/leader/lease.rs +98 -19
- package/crates/team-agent/src/leader/rediscover/tests.rs +21 -7
- package/crates/team-agent/src/leader/rediscover.rs +16 -7
- package/crates/team-agent/src/leader/start.rs +25 -12
- package/crates/team-agent/src/leader/tests/byte_findings.rs +11 -2
- package/crates/team-agent/src/leader/tests/lease_claim.rs +248 -7
- package/crates/team-agent/src/lifecycle/launch.rs +116 -100
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +5 -12
- package/crates/team-agent/src/lifecycle/tests/core.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +16 -15
- package/crates/team-agent/src/lifecycle/types.rs +1 -1
- package/crates/team-agent/src/messaging/leader_receiver.rs +22 -8
- package/crates/team-agent/src/messaging/send.rs +21 -4
- package/crates/team-agent/src/provider/adapter.rs +22 -0
- package/crates/team-agent/src/provider/session/capture.rs +228 -0
- package/crates/team-agent/src/state/identity.rs +42 -12
- package/crates/team-agent/src/state/identity_keys.rs +134 -0
- package/crates/team-agent/src/state/mod.rs +8 -0
- package/crates/team-agent/src/state/owner_gate.rs +11 -1
- package/crates/team-agent/src/state/ownership.rs +556 -0
- package/crates/team-agent/src/state/paths.rs +358 -0
- package/crates/team-agent/src/state/persist.rs +43 -5
- package/crates/team-agent/src/state/projection.rs +13 -5
- package/crates/team-agent/src/tmux_backend.rs +12 -0
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +3 -3
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
|
@@ -106,7 +106,6 @@ pub fn cmd_quick_start(args: &QuickStartArgs) -> Result<CmdResult, CliError> {
|
|
|
106
106
|
args.name.as_deref(),
|
|
107
107
|
args.team_id.as_deref(),
|
|
108
108
|
args.yes,
|
|
109
|
-
args.fresh,
|
|
110
109
|
!args.no_display,
|
|
111
110
|
)?;
|
|
112
111
|
let readiness = value.get("readiness").and_then(Value::as_object);
|
|
@@ -213,7 +212,7 @@ fn warn_ignored_owner_team_id(team_dir: &std::path::Path) {
|
|
|
213
212
|
/// `cmd_status`(`commands.py:90`)。三态:`--summary`(xor json,xor agent)→五行文本;
|
|
214
213
|
/// `--json`→`status_port::status(compact=!detail)`;else→`status_port::format_status(agent)`。
|
|
215
214
|
pub fn cmd_status(args: &StatusArgs) -> Result<CmdResult, CliError> {
|
|
216
|
-
cmd_status_for_team(args,
|
|
215
|
+
cmd_status_for_team(args, args.team.as_deref())
|
|
217
216
|
}
|
|
218
217
|
|
|
219
218
|
pub fn cmd_status_for_team(args: &StatusArgs, team: Option<&str>) -> Result<CmdResult, CliError> {
|
|
@@ -326,12 +325,20 @@ pub fn cmd_watch(args: &WatchArgs) -> Result<CmdResult, CliError> {
|
|
|
326
325
|
|
|
327
326
|
/// `cmd_sessions`(`parser.py:230`)。
|
|
328
327
|
pub fn cmd_sessions(args: &SessionsArgs) -> Result<CmdResult, CliError> {
|
|
329
|
-
let
|
|
330
|
-
|
|
328
|
+
let selected = crate::state::selector::resolve_active_team(
|
|
329
|
+
&args.workspace,
|
|
330
|
+
args.team.as_deref(),
|
|
331
|
+
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
332
|
+
)
|
|
333
|
+
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
334
|
+
let spec = match selected.spec_path.as_ref() {
|
|
335
|
+
Some(path) => load_team_spec_at(path)?,
|
|
336
|
+
None => load_team_spec_optional(&selected.run_workspace, &selected.state)?,
|
|
337
|
+
};
|
|
331
338
|
Ok(CmdResult::from_json(
|
|
332
339
|
json!({
|
|
333
340
|
"ok": true,
|
|
334
|
-
"sessions": sessions_overview(&state, spec.as_ref()),
|
|
341
|
+
"sessions": sessions_overview(&selected.state, spec.as_ref()),
|
|
335
342
|
"workspace": args.workspace.to_string_lossy().to_string(),
|
|
336
343
|
}),
|
|
337
344
|
args.json,
|
|
@@ -367,7 +374,7 @@ pub fn cmd_validate_result(args: &ValidateResultArgs) -> Result<CmdResult, CliEr
|
|
|
367
374
|
|
|
368
375
|
/// `cmd_collect`(`parser.py:292`)。
|
|
369
376
|
pub fn cmd_collect(args: &CollectArgs) -> Result<CmdResult, CliError> {
|
|
370
|
-
cmd_collect_for_team(args,
|
|
377
|
+
cmd_collect_for_team(args, args.team.as_deref())
|
|
371
378
|
}
|
|
372
379
|
|
|
373
380
|
pub fn cmd_collect_for_team(args: &CollectArgs, team: Option<&str>) -> Result<CmdResult, CliError> {
|
|
@@ -599,6 +606,11 @@ fn write_settle_details_log(workspace: &Path, collect: &Value, status: &Value) -
|
|
|
599
606
|
|
|
600
607
|
/// `cmd_allow_peer_talk`(`parser.py allow-peer-talk`).
|
|
601
608
|
pub fn cmd_allow_peer_talk(args: &AllowPeerTalkArgs) -> Result<CmdResult, CliError> {
|
|
609
|
+
if args.team.is_some() {
|
|
610
|
+
return Err(CliError::Usage(
|
|
611
|
+
"allow-peer-talk --team is not supported yet because peer allowlist storage is not team-scoped".to_string(),
|
|
612
|
+
));
|
|
613
|
+
}
|
|
602
614
|
let value = messaging::allow_peer_talk(&args.workspace, &args.a, &args.b)?;
|
|
603
615
|
Ok(CmdResult::from_json(value, args.json))
|
|
604
616
|
}
|
|
@@ -613,7 +625,7 @@ pub fn cmd_repair_state(args: &RepairStateArgs) -> Result<CmdResult, CliError> {
|
|
|
613
625
|
}
|
|
614
626
|
let selected = crate::state::selector::resolve_active_team(
|
|
615
627
|
&args.workspace,
|
|
616
|
-
|
|
628
|
+
args.team.as_deref(),
|
|
617
629
|
crate::state::selector::SelectorMode::RequireSpec,
|
|
618
630
|
)
|
|
619
631
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
@@ -666,7 +678,7 @@ pub fn cmd_repair_state(args: &RepairStateArgs) -> Result<CmdResult, CliError> {
|
|
|
666
678
|
pub fn cmd_diagnose(args: &DiagnoseArgs) -> Result<CmdResult, CliError> {
|
|
667
679
|
let selected = crate::state::selector::resolve_active_team(
|
|
668
680
|
&args.workspace,
|
|
669
|
-
|
|
681
|
+
args.team.as_deref(),
|
|
670
682
|
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
671
683
|
)
|
|
672
684
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
@@ -687,8 +699,8 @@ pub fn cmd_diagnose(args: &DiagnoseArgs) -> Result<CmdResult, CliError> {
|
|
|
687
699
|
"session_name": state.get("session_name").cloned().unwrap_or(Value::Null),
|
|
688
700
|
"leader_receiver": state.get("leader_receiver").cloned().unwrap_or(Value::Null),
|
|
689
701
|
"agent_count": state.get("agents").and_then(Value::as_object).map_or(0, serde_json::Map::len),
|
|
690
|
-
"message_count": count_dir_entries(&
|
|
691
|
-
"result_count": count_dir_entries(&
|
|
702
|
+
"message_count": count_dir_entries(&selected.run_workspace.join(".team").join("messages")),
|
|
703
|
+
"result_count": count_dir_entries(&selected.run_workspace.join(".team").join("results")),
|
|
692
704
|
},
|
|
693
705
|
"suggested_repairs": suggested_repairs,
|
|
694
706
|
}),
|
|
@@ -707,7 +719,7 @@ pub fn cmd_preflight(args: &PreflightArgs) -> Result<CmdResult, CliError> {
|
|
|
707
719
|
|
|
708
720
|
/// `cmd_wait_ready`(`parser.py:171`)。
|
|
709
721
|
pub fn cmd_wait_ready(args: &WaitReadyArgs) -> Result<CmdResult, CliError> {
|
|
710
|
-
let report = build_wait_ready_report(&args.workspace, args.timeout)?;
|
|
722
|
+
let report = build_wait_ready_report(&args.workspace, args.timeout, args.team.as_deref())?;
|
|
711
723
|
Ok(CmdResult::from_json(
|
|
712
724
|
report,
|
|
713
725
|
args.json,
|
|
@@ -1321,7 +1333,18 @@ fn load_team_spec_at(spec_path: &Path) -> Result<Option<crate::model::yaml::Valu
|
|
|
1321
1333
|
|
|
1322
1334
|
/// `cmd_approvals`(`commands.py:112`)。
|
|
1323
1335
|
pub fn cmd_approvals(args: &ApprovalsArgs) -> Result<CmdResult, CliError> {
|
|
1324
|
-
let
|
|
1336
|
+
let selected = crate::state::selector::resolve_active_team(
|
|
1337
|
+
&args.workspace,
|
|
1338
|
+
args.team.as_deref(),
|
|
1339
|
+
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
1340
|
+
)
|
|
1341
|
+
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
1342
|
+
let value = status_port::approvals_scoped(
|
|
1343
|
+
&selected.run_workspace,
|
|
1344
|
+
&selected.state,
|
|
1345
|
+
args.agent.as_deref(),
|
|
1346
|
+
args.json,
|
|
1347
|
+
)?;
|
|
1325
1348
|
if args.json {
|
|
1326
1349
|
Ok(CmdResult::from_json(value, true))
|
|
1327
1350
|
} else {
|
|
@@ -1331,21 +1354,29 @@ pub fn cmd_approvals(args: &ApprovalsArgs) -> Result<CmdResult, CliError> {
|
|
|
1331
1354
|
|
|
1332
1355
|
/// `cmd_inbox`(`commands.py:137`)。
|
|
1333
1356
|
pub fn cmd_inbox(args: &InboxArgs) -> Result<CmdResult, CliError> {
|
|
1334
|
-
let
|
|
1357
|
+
let selected = crate::state::selector::resolve_active_team(
|
|
1335
1358
|
&args.workspace,
|
|
1359
|
+
args.team.as_deref(),
|
|
1360
|
+
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
1361
|
+
)
|
|
1362
|
+
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
1363
|
+
let value = status_port::inbox(
|
|
1364
|
+
&selected.run_workspace,
|
|
1336
1365
|
&args.agent,
|
|
1337
1366
|
args.limit,
|
|
1338
1367
|
args.since.as_deref(),
|
|
1339
1368
|
args.json,
|
|
1369
|
+
Some(&selected.team_key),
|
|
1340
1370
|
)?;
|
|
1341
1371
|
if args.json {
|
|
1342
1372
|
Ok(CmdResult::from_json(value, true))
|
|
1343
1373
|
} else {
|
|
1344
1374
|
Ok(CmdResult::human(format_inbox_human(
|
|
1345
|
-
&
|
|
1375
|
+
&selected.run_workspace,
|
|
1346
1376
|
&args.agent,
|
|
1347
1377
|
args.since.as_deref(),
|
|
1348
1378
|
&value,
|
|
1379
|
+
Some(&selected.team_key),
|
|
1349
1380
|
)?))
|
|
1350
1381
|
}
|
|
1351
1382
|
}
|
|
@@ -1355,6 +1386,7 @@ fn format_inbox_human(
|
|
|
1355
1386
|
agent: &str,
|
|
1356
1387
|
since: Option<&str>,
|
|
1357
1388
|
value: &Value,
|
|
1389
|
+
owner_team_id: Option<&str>,
|
|
1358
1390
|
) -> Result<String, CliError> {
|
|
1359
1391
|
let messages = value
|
|
1360
1392
|
.get("messages")
|
|
@@ -1377,7 +1409,7 @@ fn format_inbox_human(
|
|
|
1377
1409
|
lines.push(format!("- {sender}: {content}"));
|
|
1378
1410
|
}
|
|
1379
1411
|
}
|
|
1380
|
-
let pending = uncollected_result_count(workspace)?;
|
|
1412
|
+
let pending = uncollected_result_count(workspace, owner_team_id)?;
|
|
1381
1413
|
let mut note = "final results are not in inbox; use team-agent collect".to_string();
|
|
1382
1414
|
if pending > 0 {
|
|
1383
1415
|
note.push_str(&format!(" ({pending} uncollected result(s) pending)"));
|
|
@@ -1386,16 +1418,23 @@ fn format_inbox_human(
|
|
|
1386
1418
|
Ok(lines.join("\n"))
|
|
1387
1419
|
}
|
|
1388
1420
|
|
|
1389
|
-
fn uncollected_result_count(workspace: &Path) -> Result<i64, CliError> {
|
|
1421
|
+
fn uncollected_result_count(workspace: &Path, owner_team_id: Option<&str>) -> Result<i64, CliError> {
|
|
1390
1422
|
let store = crate::message_store::MessageStore::open(workspace)
|
|
1391
1423
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
1392
1424
|
let conn = crate::db::schema::open_db(store.db_path())
|
|
1393
1425
|
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1426
|
+
match owner_team_id {
|
|
1427
|
+
Some(team) => conn.query_row(
|
|
1428
|
+
"select count(*) from results where status not in ('collected', 'invalid') and owner_team_id = ?1",
|
|
1429
|
+
[team],
|
|
1430
|
+
|row| row.get::<_, i64>(0),
|
|
1431
|
+
),
|
|
1432
|
+
None => conn.query_row(
|
|
1433
|
+
"select count(*) from results where status not in ('collected', 'invalid')",
|
|
1434
|
+
[],
|
|
1435
|
+
|row| row.get::<_, i64>(0),
|
|
1436
|
+
),
|
|
1437
|
+
}
|
|
1399
1438
|
.map_err(|e| CliError::Runtime(e.to_string()))
|
|
1400
1439
|
}
|
|
1401
1440
|
|
|
@@ -1527,11 +1566,32 @@ pub fn cmd_remove_agent(args: &RemoveAgentArgs) -> Result<CmdResult, CliError> {
|
|
|
1527
1566
|
|
|
1528
1567
|
/// `cmd_stuck_list`(`commands.py:405`)。REUSE `messaging::stuck_list`。
|
|
1529
1568
|
pub fn cmd_stuck_list(args: &StuckListArgs) -> Result<CmdResult, CliError> {
|
|
1530
|
-
|
|
1569
|
+
let selected = crate::state::selector::resolve_active_team(
|
|
1570
|
+
&args.workspace,
|
|
1571
|
+
args.team.as_deref(),
|
|
1572
|
+
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
1573
|
+
)
|
|
1574
|
+
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
1575
|
+
let suppressed = selected
|
|
1576
|
+
.state
|
|
1577
|
+
.get("coordinator")
|
|
1578
|
+
.and_then(|v| v.get("suppressed_idle_alerts"))
|
|
1579
|
+
.and_then(|v| v.get(&selected.team_key))
|
|
1580
|
+
.cloned()
|
|
1581
|
+
.unwrap_or_else(|| json!({}));
|
|
1582
|
+
Ok(CmdResult::from_json(
|
|
1583
|
+
json!({"ok": true, "suppressed_idle_alerts": suppressed}),
|
|
1584
|
+
args.json,
|
|
1585
|
+
))
|
|
1531
1586
|
}
|
|
1532
1587
|
|
|
1533
1588
|
/// `cmd_stuck_cancel`(`commands.py:409`)。REUSE `messaging::stuck_cancel`(suppressed_by="leader")。
|
|
1534
1589
|
pub fn cmd_stuck_cancel(args: &StuckCancelArgs) -> Result<CmdResult, CliError> {
|
|
1590
|
+
if args.team.is_some() {
|
|
1591
|
+
return Err(CliError::Usage(
|
|
1592
|
+
"stuck-cancel --team is not supported yet because stuck suppression storage is not team-scoped".to_string(),
|
|
1593
|
+
));
|
|
1594
|
+
}
|
|
1535
1595
|
Ok(CmdResult::from_json(
|
|
1536
1596
|
messaging::stuck_cancel(&args.workspace, &args.agent, args.alert_type, "leader")?,
|
|
1537
1597
|
args.json,
|
|
@@ -297,13 +297,17 @@ fn copy_optional_field(from: &Value, to: &mut Value, key: &str) {
|
|
|
297
297
|
obj.insert(key.to_string(), value);
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
pub(crate) fn build_wait_ready_report(
|
|
300
|
+
pub(crate) fn build_wait_ready_report(
|
|
301
|
+
workspace: &std::path::Path,
|
|
302
|
+
timeout: f64,
|
|
303
|
+
team: Option<&str>,
|
|
304
|
+
) -> Result<Value, CliError> {
|
|
301
305
|
// swallow batch 3 ③: an unreadable runtime state must never read as "ready" — the
|
|
302
306
|
// read error is surfaced verbatim (state_read_error) with ready=false instead of
|
|
303
307
|
// silently degrading to an empty/stale state.
|
|
304
308
|
let selected = match crate::state::selector::resolve_active_team(
|
|
305
309
|
workspace,
|
|
306
|
-
|
|
310
|
+
team,
|
|
307
311
|
crate::state::selector::SelectorMode::RuntimeOnly,
|
|
308
312
|
) {
|
|
309
313
|
Ok(selected) => selected,
|