@team-agent/installer 0.5.99 → 0.5.101
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 +1 -156
- package/crates/team-agent/src/cli/diagnose.rs +32 -10
- package/crates/team-agent/src/cli/emit.rs +6 -185
- package/crates/team-agent/src/cli/mod.rs +32 -182
- package/crates/team-agent/src/cli/send/coordinator.rs +2 -2
- package/crates/team-agent/src/cli/spec.rs +41 -67
- package/crates/team-agent/src/cli/tests/main_preserved.rs +2 -185
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +9 -9
- package/crates/team-agent/src/cli/triage.rs +1 -1
- package/crates/team-agent/src/cli/types.rs +0 -49
- package/crates/team-agent/src/compiler.rs +3 -0
- package/crates/team-agent/src/leader/lease.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +211 -16
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +15 -5
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +89 -6
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +168 -32
- package/crates/team-agent/src/lifecycle/launch.rs +11 -3
- package/crates/team-agent/src/lifecycle/profile_launch.rs +23 -8
- package/crates/team-agent/src/lifecycle/restart/agent.rs +2 -2
- package/crates/team-agent/src/lifecycle/restart/avatar.rs +1806 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +114 -1
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +14 -8
- package/crates/team-agent/src/lifecycle/restart/selection.rs +14 -2
- package/crates/team-agent/src/lifecycle/restart.rs +11 -4
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +9 -2
- package/crates/team-agent/src/lifecycle/tests/pi_fork_session_inheritance_red.rs +1398 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +0 -60
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests.rs +1 -0
- package/crates/team-agent/src/lifecycle/types.rs +10 -2
- package/crates/team-agent/src/messaging/send.rs +2 -2
- package/crates/team-agent/src/model/spec.rs +1 -0
- package/crates/team-agent/src/provider/types.rs +3 -1
- package/crates/team-agent/src/state/persist.rs +58 -2
- package/crates/team-agent/src/state/projection.rs +5 -1
- package/crates/team-agent/src/state/repository.rs +125 -9
- package/crates/team-agent/src/transport/test_support.rs +23 -3
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +17 -46
- package/skills/team-agent/references/recovery-runbook.md +2 -2
- package/skills/team-agent/references/team-agent-operator.md +4 -2
- package/skills/team-agent/references/team-in-team.md +0 -6
|
@@ -954,7 +954,7 @@ pub(crate) fn lifecycle_worker_transport_for_selected_state(
|
|
|
954
954
|
/// returns: 后端与 endpoint 来源
|
|
955
955
|
/// errors: state 声明后端是 conpty 时返回 TeamSelect
|
|
956
956
|
/// ---
|
|
957
|
-
pub(
|
|
957
|
+
pub(crate) fn lifecycle_worker_tmux_backend_selection_for_state(
|
|
958
958
|
run_workspace: &Path,
|
|
959
959
|
state: &serde_json::Value,
|
|
960
960
|
) -> Result<crate::tmux_backend::RuntimeTmuxBackendSelection, LifecycleError> {
|
|
@@ -1344,6 +1344,119 @@ pub(crate) fn session_identity_probe_for_agent(
|
|
|
1344
1344
|
}
|
|
1345
1345
|
}
|
|
1346
1346
|
|
|
1347
|
+
/// ---
|
|
1348
|
+
/// purpose: 将合法 Pi fork 血统纳入共享会话身份判定
|
|
1349
|
+
/// params:
|
|
1350
|
+
/// workspace/state/agent: 当前被验证席位及同队来源记录
|
|
1351
|
+
/// rollout_path: 当前席位的精确 backing
|
|
1352
|
+
/// returns: 字面身份一致,或经独立 Pi backing 与 fork 血统验证后合法继承时为 true
|
|
1353
|
+
/// ---
|
|
1354
|
+
pub(crate) fn session_identity_probe_for_agent_with_fork_ancestry(
|
|
1355
|
+
workspace: &Path,
|
|
1356
|
+
state: &serde_json::Value,
|
|
1357
|
+
agent_id: &AgentId,
|
|
1358
|
+
agent: &serde_json::Value,
|
|
1359
|
+
provider: Provider,
|
|
1360
|
+
rollout_path: Option<&RolloutPath>,
|
|
1361
|
+
) -> SessionIdentityProbeResult {
|
|
1362
|
+
let mut probe = session_identity_probe_for_agent(agent_id, provider, rollout_path);
|
|
1363
|
+
if probe.identity_ok != Some(false)
|
|
1364
|
+
|| !pi_fork_ancestor_identity_is_valid(
|
|
1365
|
+
workspace,
|
|
1366
|
+
state,
|
|
1367
|
+
agent_id,
|
|
1368
|
+
agent,
|
|
1369
|
+
provider,
|
|
1370
|
+
rollout_path,
|
|
1371
|
+
probe.embedded_agent_id.as_deref(),
|
|
1372
|
+
)
|
|
1373
|
+
{
|
|
1374
|
+
return probe;
|
|
1375
|
+
}
|
|
1376
|
+
probe.identity_ok = Some(true);
|
|
1377
|
+
probe
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
fn pi_fork_ancestor_identity_is_valid(
|
|
1381
|
+
workspace: &Path,
|
|
1382
|
+
state: &serde_json::Value,
|
|
1383
|
+
agent_id: &AgentId,
|
|
1384
|
+
agent: &serde_json::Value,
|
|
1385
|
+
provider: Provider,
|
|
1386
|
+
rollout_path: Option<&RolloutPath>,
|
|
1387
|
+
embedded_agent_id: Option<&str>,
|
|
1388
|
+
) -> bool {
|
|
1389
|
+
if provider != Provider::Pi
|
|
1390
|
+
|| agent.get("attribution_ambiguous").and_then(serde_json::Value::as_bool) == Some(true)
|
|
1391
|
+
|| agent.get("capture_state").and_then(serde_json::Value::as_str)
|
|
1392
|
+
== Some("attribution_ambiguous")
|
|
1393
|
+
|| !matches!(
|
|
1394
|
+
agent
|
|
1395
|
+
.get("attribution_confidence")
|
|
1396
|
+
.and_then(serde_json::Value::as_str),
|
|
1397
|
+
Some("high" | "medium")
|
|
1398
|
+
)
|
|
1399
|
+
{
|
|
1400
|
+
return false;
|
|
1401
|
+
}
|
|
1402
|
+
let (Some(embedded_agent_id), Some(forked_from), Some(target_path)) = (
|
|
1403
|
+
embedded_agent_id,
|
|
1404
|
+
agent
|
|
1405
|
+
.get("forked_from")
|
|
1406
|
+
.and_then(serde_json::Value::as_str)
|
|
1407
|
+
.filter(|value| !value.is_empty()),
|
|
1408
|
+
rollout_path.map(RolloutPath::as_path),
|
|
1409
|
+
) else {
|
|
1410
|
+
return false;
|
|
1411
|
+
};
|
|
1412
|
+
if forked_from != embedded_agent_id || forked_from == agent_id.as_str() {
|
|
1413
|
+
return false;
|
|
1414
|
+
}
|
|
1415
|
+
let Some(source_agent) = state
|
|
1416
|
+
.get("agents")
|
|
1417
|
+
.and_then(|agents| agents.get(forked_from))
|
|
1418
|
+
.filter(|source| agent_provider(source) == Provider::Pi)
|
|
1419
|
+
else {
|
|
1420
|
+
return false;
|
|
1421
|
+
};
|
|
1422
|
+
let Some(source_path) = agent_rollout_path(source_agent) else {
|
|
1423
|
+
return false;
|
|
1424
|
+
};
|
|
1425
|
+
if source_path.as_path() == target_path {
|
|
1426
|
+
return false;
|
|
1427
|
+
}
|
|
1428
|
+
let (Some(session_id), Ok(target_path), Some(target_root)) = (
|
|
1429
|
+
agent_session_id(agent),
|
|
1430
|
+
std::fs::canonicalize(target_path),
|
|
1431
|
+
agent
|
|
1432
|
+
.get("claude_projects_root")
|
|
1433
|
+
.and_then(serde_json::Value::as_str)
|
|
1434
|
+
.filter(|path| !path.is_empty())
|
|
1435
|
+
.map(std::path::PathBuf::from),
|
|
1436
|
+
) else {
|
|
1437
|
+
return false;
|
|
1438
|
+
};
|
|
1439
|
+
let Ok(target_root) = std::fs::canonicalize(target_root) else {
|
|
1440
|
+
return false;
|
|
1441
|
+
};
|
|
1442
|
+
if !target_path.starts_with(target_root)
|
|
1443
|
+
|| std::fs::canonicalize(source_path.as_path())
|
|
1444
|
+
.is_ok_and(|source_path| source_path == target_path)
|
|
1445
|
+
|| !resume_backing_probe_for_agent(
|
|
1446
|
+
workspace,
|
|
1447
|
+
agent_id,
|
|
1448
|
+
agent,
|
|
1449
|
+
provider,
|
|
1450
|
+
&session_id,
|
|
1451
|
+
rollout_path,
|
|
1452
|
+
)
|
|
1453
|
+
.exists
|
|
1454
|
+
{
|
|
1455
|
+
return false;
|
|
1456
|
+
}
|
|
1457
|
+
true
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1347
1460
|
/// ---
|
|
1348
1461
|
/// purpose: 按 provider 分支探测 resume 依据,并记录所有查过的路径
|
|
1349
1462
|
/// returns: 存在位与查过的路径列表;持久化的 rollout 路径即使不存在也记进列表
|
|
@@ -336,7 +336,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
336
336
|
.first()
|
|
337
337
|
.cloned()
|
|
338
338
|
.unwrap_or_else(|| "dirty_topology".to_string()),
|
|
339
|
-
error: "restart refused: tmux endpoint/socket topology is inconsistent; run
|
|
339
|
+
error: "restart refused: tmux endpoint/socket topology is inconsistent; run doctor from the intended leader socket before restarting".to_string(),
|
|
340
340
|
issue_ids: topology_issue_ids,
|
|
341
341
|
});
|
|
342
342
|
}
|
|
@@ -1133,8 +1133,11 @@ fn restart_with_selected_team_and_transport(
|
|
|
1133
1133
|
&session_id,
|
|
1134
1134
|
agent_rollout_path(&agent).as_ref(),
|
|
1135
1135
|
);
|
|
1136
|
-
let identity_probe =
|
|
1136
|
+
let identity_probe = session_identity_probe_for_agent_with_fork_ancestry(
|
|
1137
|
+
&selected.run_workspace,
|
|
1138
|
+
&state,
|
|
1137
1139
|
&decision.agent_id,
|
|
1140
|
+
&agent,
|
|
1138
1141
|
provider,
|
|
1139
1142
|
agent_rollout_path(&agent).as_ref(),
|
|
1140
1143
|
);
|
|
@@ -1221,8 +1224,11 @@ fn restart_with_selected_team_and_transport(
|
|
|
1221
1224
|
&session_id,
|
|
1222
1225
|
agent_rollout_path(&agent_after).as_ref(),
|
|
1223
1226
|
);
|
|
1224
|
-
let identity_probe_after =
|
|
1227
|
+
let identity_probe_after = session_identity_probe_for_agent_with_fork_ancestry(
|
|
1228
|
+
&selected.run_workspace,
|
|
1229
|
+
&state,
|
|
1225
1230
|
&decision.agent_id,
|
|
1231
|
+
&agent_after,
|
|
1226
1232
|
provider,
|
|
1227
1233
|
agent_rollout_path(&agent_after).as_ref(),
|
|
1228
1234
|
);
|
|
@@ -1804,7 +1810,7 @@ fn restart_readiness_timeout_message(
|
|
|
1804
1810
|
- tmux session created: {session}\n\
|
|
1805
1811
|
- worker pane addressable: {pane}\n\
|
|
1806
1812
|
- coordinator alive: {coordinator}\n\
|
|
1807
|
-
Action: check coordinator log {log}, then inspect this workspace with `team-agent
|
|
1813
|
+
Action: check coordinator log {log}, then inspect this workspace with `team-agent doctor --workspace '{workspace}'`\n\
|
|
1808
1814
|
Log: coordinator_log={log} state={state} pid_file={pid}",
|
|
1809
1815
|
missing = restart_readiness_missing_summary(readiness),
|
|
1810
1816
|
session = yes_no(readiness.session_created),
|
|
@@ -2703,7 +2709,7 @@ fn restart_failure_phase(
|
|
|
2703
2709
|
// 0.5.39 Slice 1 (tmux-server-death-locate §11.1 B): promote
|
|
2704
2710
|
// spawn/readiness errors whose stderr contains "server exited
|
|
2705
2711
|
// unexpectedly" to `tmux_server_crashed`, so downstream event
|
|
2706
|
-
// classification and
|
|
2712
|
+
// classification and doctor next_actions ("team-agent doctor")
|
|
2707
2713
|
// don't misattribute a whole-server death to a per-agent provider
|
|
2708
2714
|
// failure. Restart itself does not recover from server crashes in
|
|
2709
2715
|
// 0.5.39 (§11.1 C is Slice 3, later car); it just classifies clearly.
|
|
@@ -2776,7 +2782,7 @@ fn write_restart_agent_failed_event(
|
|
|
2776
2782
|
"phase": phase,
|
|
2777
2783
|
"error": error,
|
|
2778
2784
|
"action": format!(
|
|
2779
|
-
"inspect worker {} output, then
|
|
2785
|
+
"inspect worker {} output, then reset that worker with `team-agent reset-agent {} --discard-session` or rerun `team-agent restart --allow-fresh`",
|
|
2780
2786
|
decision.agent_id,
|
|
2781
2787
|
decision.agent_id
|
|
2782
2788
|
),
|
|
@@ -3262,7 +3268,7 @@ fn restart_failure_next_actions(failed_agents: &[RestartFailedAgent]) -> Vec<Str
|
|
|
3262
3268
|
.iter()
|
|
3263
3269
|
.map(|failure| {
|
|
3264
3270
|
format!(
|
|
3265
|
-
"inspect worker {} output, then
|
|
3271
|
+
"inspect worker {} output, then reset that worker with `team-agent reset-agent {} --discard-session` or rerun `team-agent restart --allow-fresh`",
|
|
3266
3272
|
failure.agent_id, failure.agent_id
|
|
3267
3273
|
)
|
|
3268
3274
|
})
|
|
@@ -4911,7 +4917,7 @@ tasks:
|
|
|
4911
4917
|
},
|
|
4912
4918
|
std::time::Duration::from_secs(30),
|
|
4913
4919
|
);
|
|
4914
|
-
assert!(message.contains("team-agent
|
|
4920
|
+
assert!(message.contains("team-agent doctor --workspace"));
|
|
4915
4921
|
assert!(message.contains(workspace.to_string_lossy().as_ref()));
|
|
4916
4922
|
assert!(!message.contains("--allow-fresh"));
|
|
4917
4923
|
assert!(!message.contains("restart <agent>"));
|
|
@@ -349,8 +349,20 @@ pub(crate) fn classify_restart_plan_with_resume_validation(
|
|
|
349
349
|
}
|
|
350
350
|
_ => (true, Vec::new()),
|
|
351
351
|
};
|
|
352
|
-
let identity_probe =
|
|
353
|
-
|
|
352
|
+
let identity_probe = workspace
|
|
353
|
+
.map(|workspace| {
|
|
354
|
+
session_identity_probe_for_agent_with_fork_ancestry(
|
|
355
|
+
workspace,
|
|
356
|
+
state,
|
|
357
|
+
&agent_id,
|
|
358
|
+
agent,
|
|
359
|
+
provider,
|
|
360
|
+
rollout_path.as_ref(),
|
|
361
|
+
)
|
|
362
|
+
})
|
|
363
|
+
.unwrap_or_else(|| {
|
|
364
|
+
session_identity_probe_for_agent(&agent_id, provider, rollout_path.as_ref())
|
|
365
|
+
});
|
|
354
366
|
let session_identity_mismatch = session_id.is_some()
|
|
355
367
|
&& provider_can_resume
|
|
356
368
|
&& resume_backing_exists
|
|
@@ -66,6 +66,8 @@ struct LifecyclePathRefs<'a> {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
mod agent;
|
|
69
|
+
mod avatar;
|
|
70
|
+
pub(crate) use avatar::fork_pi_new_seat_locked;
|
|
69
71
|
mod common;
|
|
70
72
|
mod orchestrator;
|
|
71
73
|
pub mod preflight;
|
|
@@ -82,11 +84,16 @@ pub(crate) use agent::{
|
|
|
82
84
|
};
|
|
83
85
|
pub(crate) use common::refresh_missing_provider_sessions;
|
|
84
86
|
pub(crate) use common::restart_required_missing_session_agent_ids;
|
|
85
|
-
pub(crate) use common::
|
|
87
|
+
pub(crate) use common::{
|
|
88
|
+
session_identity_probe_for_agent, session_identity_probe_for_agent_with_fork_ancestry,
|
|
89
|
+
};
|
|
86
90
|
// 0.3.24 add-agent socket drift fix: state-aware tmux resolver shared with
|
|
87
91
|
// `lifecycle::launch::add_agent` / `fork_agent` so all three (restart / add / fork)
|
|
88
92
|
// route to the SAME tmux socket the live team uses.
|
|
89
|
-
pub(crate) use common::
|
|
93
|
+
pub(crate) use common::{
|
|
94
|
+
lifecycle_worker_tmux_backend_for_selected_state,
|
|
95
|
+
lifecycle_worker_tmux_backend_selection_for_state,
|
|
96
|
+
};
|
|
90
97
|
pub(crate) use orchestrator::{halt_plan, plan_status};
|
|
91
98
|
pub(crate) use rebuild::restart_with_transport_with_session_convergence_deadline;
|
|
92
99
|
// 0.5.38 (`.team/artifacts/startup-latency-locate.md` §5): expose the phase
|
|
@@ -100,10 +107,10 @@ pub(crate) use rebuild::{
|
|
|
100
107
|
restart_candidates, restart_with_transport, restart_with_transport_with_readiness_deadline,
|
|
101
108
|
select_restart_state,
|
|
102
109
|
};
|
|
103
|
-
pub use remove::{remove_agent, remove_agent_flag_requirements};
|
|
104
110
|
pub(crate) use remove::remove_agent_with_transport;
|
|
105
|
-
pub
|
|
111
|
+
pub use remove::{remove_agent, remove_agent_flag_requirements};
|
|
106
112
|
pub use selection::decide_start_mode;
|
|
113
|
+
pub(crate) use selection::{classify_first_send_at, classify_restart_plan, python_type_name};
|
|
107
114
|
// Layer 2 (leader follow-up 2026-06-22): test-visible workspace-aware
|
|
108
115
|
// classification so lifecycle/tests/restart.rs can exercise the
|
|
109
116
|
// SessionBackingStoreMissing + checked_paths + RecoveryHint path
|
|
@@ -122,7 +122,7 @@ fn stale_snapshot_cannot_flip_status_or_diagnose_ok_readiness() {
|
|
|
122
122
|
"--json",
|
|
123
123
|
]);
|
|
124
124
|
let diagnose_before = case.run_json(&[
|
|
125
|
-
"
|
|
125
|
+
"doctor",
|
|
126
126
|
"--workspace",
|
|
127
127
|
case.workspace_str(),
|
|
128
128
|
"--team",
|
|
@@ -142,7 +142,7 @@ fn stale_snapshot_cannot_flip_status_or_diagnose_ok_readiness() {
|
|
|
142
142
|
"--json",
|
|
143
143
|
]);
|
|
144
144
|
let diagnose_after = case.run_json(&[
|
|
145
|
-
"
|
|
145
|
+
"doctor",
|
|
146
146
|
"--workspace",
|
|
147
147
|
case.workspace_str(),
|
|
148
148
|
"--team",
|
|
@@ -182,7 +182,7 @@ fn diagnose_missing_profile_is_rc1_ok_false_and_zero_token() {
|
|
|
182
182
|
.unwrap();
|
|
183
183
|
|
|
184
184
|
let output = Command::new(crate::lifecycle::tests::test_binary_path())
|
|
185
|
-
.args(["
|
|
185
|
+
.args(["doctor", team.to_str().unwrap(), "--workspace", ws.to_str().unwrap(), "--json"])
|
|
186
186
|
.current_dir(&team)
|
|
187
187
|
.output()
|
|
188
188
|
.expect("diagnose subprocess should run");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use super::launch_spawn::seed_healthy_coordinator;
|
|
2
2
|
use super::*;
|
|
3
3
|
use crate::cli::{
|
|
4
|
-
|
|
4
|
+
cmd_doctor, cmd_send, cmd_status, lifecycle_port, CmdOutput, CmdResult, DoctorArgs,
|
|
5
5
|
SendArgs, StatusArgs,
|
|
6
6
|
};
|
|
7
7
|
use crate::transport::test_support::OfflineTransport;
|
|
@@ -582,10 +582,17 @@ fn run_diagnose_after_quick_start() -> Value {
|
|
|
582
582
|
.is_some(),
|
|
583
583
|
"index-missing diagnose requires a persisted seeded owner"
|
|
584
584
|
);
|
|
585
|
-
let diagnose =
|
|
585
|
+
let diagnose = cmd_doctor(&DoctorArgs {
|
|
586
586
|
workspace,
|
|
587
587
|
json: true,
|
|
588
588
|
team: Some("teamdir".to_string()),
|
|
589
|
+
spec: None,
|
|
590
|
+
gate: None,
|
|
591
|
+
comms: false,
|
|
592
|
+
fix: false,
|
|
593
|
+
fix_schema: false,
|
|
594
|
+
cleanup_orphans: false,
|
|
595
|
+
confirm: false,
|
|
589
596
|
});
|
|
590
597
|
match cmd_value(diagnose) {
|
|
591
598
|
Value::Object(map) => map
|