@team-agent/installer 0.5.41 → 0.5.43
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 +129 -54
- package/crates/team-agent/src/cli/diagnose.rs +1 -2
- package/crates/team-agent/src/cli/emit.rs +1 -7
- package/crates/team-agent/src/cli/helpers.rs +3 -1
- package/crates/team-agent/src/cli/leader.rs +2 -1
- package/crates/team-agent/src/cli/mod.rs +27 -7
- package/crates/team-agent/src/cli/named_address.rs +14 -5
- package/crates/team-agent/src/cli/profile.rs +19 -7
- package/crates/team-agent/src/cli/send.rs +9 -3
- package/crates/team-agent/src/cli/status.rs +8 -30
- package/crates/team-agent/src/cli/status_port.rs +1341 -1272
- package/crates/team-agent/src/cli/tests/base.rs +738 -660
- package/crates/team-agent/src/cli/tests/compile.rs +45 -18
- package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
- package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
- package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
- package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
- package/crates/team-agent/src/cli/tests/mod.rs +17 -7
- package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
- package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
- package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
- package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
- package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
- package/crates/team-agent/src/codex_app_server.rs +2 -5
- package/crates/team-agent/src/compiler/tests.rs +139 -33
- package/crates/team-agent/src/compiler.rs +55 -22
- package/crates/team-agent/src/conpty/backend.rs +23 -33
- package/crates/team-agent/src/coordinator/backoff.rs +2 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
- package/crates/team-agent/src/coordinator/health.rs +46 -31
- package/crates/team-agent/src/coordinator/mod.rs +3 -3
- package/crates/team-agent/src/coordinator/orphan.rs +22 -10
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +47 -52
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
- package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
- package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
- package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
- package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
- package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
- package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
- package/crates/team-agent/src/coordinator/tick.rs +19 -1
- package/crates/team-agent/src/db/message_store.rs +138 -30
- package/crates/team-agent/src/db/migration.rs +249 -61
- package/crates/team-agent/src/db/schema.rs +303 -82
- package/crates/team-agent/src/diagnose/comms.rs +9 -2
- package/crates/team-agent/src/diagnose/mod.rs +1 -3
- package/crates/team-agent/src/diagnose/orphans.rs +79 -61
- package/crates/team-agent/src/event_log.rs +70 -16
- package/crates/team-agent/src/layout/manager.rs +15 -4
- package/crates/team-agent/src/layout/mod.rs +4 -4
- package/crates/team-agent/src/layout/overlay.rs +10 -3
- package/crates/team-agent/src/layout/placement.rs +5 -1
- package/crates/team-agent/src/layout/recovery.rs +4 -2
- package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
- package/crates/team-agent/src/layout/sessions.rs +17 -9
- package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
- package/crates/team-agent/src/layout/worker_env.rs +52 -19
- package/crates/team-agent/src/leader/helpers.rs +7 -1
- package/crates/team-agent/src/leader/lease.rs +195 -82
- package/crates/team-agent/src/leader/owner_bind.rs +55 -22
- package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
- package/crates/team-agent/src/leader/rediscover.rs +74 -25
- package/crates/team-agent/src/leader/start.rs +75 -54
- package/crates/team-agent/src/leader/takeover.rs +46 -11
- package/crates/team-agent/src/leader/tests/basics.rs +320 -167
- package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
- package/crates/team-agent/src/leader/tests/identity.rs +428 -356
- package/crates/team-agent/src/leader/tests/idle.rs +285 -254
- package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
- package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
- package/crates/team-agent/src/leader/tests/mod.rs +115 -99
- package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
- package/crates/team-agent/src/lib.rs +4 -4
- package/crates/team-agent/src/lifecycle/display.rs +7 -3
- package/crates/team-agent/src/lifecycle/launch.rs +6 -1
- package/crates/team-agent/src/lifecycle/mod.rs +9 -1
- package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
- package/crates/team-agent/src/lifecycle/tests.rs +2 -2
- package/crates/team-agent/src/main.rs +4 -4
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
- package/crates/team-agent/src/mcp_server/mod.rs +11 -2
- package/crates/team-agent/src/mcp_server/types.rs +14 -3
- package/crates/team-agent/src/mcp_server/wire.rs +230 -68
- package/crates/team-agent/src/messaging/delivery.rs +20 -18
- package/crates/team-agent/src/messaging/helpers.rs +25 -4
- package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
- package/crates/team-agent/src/messaging/mod.rs +2 -3
- package/crates/team-agent/src/messaging/selftest.rs +46 -26
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
- package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
- package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
- package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
- package/crates/team-agent/src/messaging/trust.rs +25 -2
- package/crates/team-agent/src/messaging/watchers.rs +37 -9
- package/crates/team-agent/src/model/enums.rs +65 -16
- package/crates/team-agent/src/model/ids.rs +12 -3
- package/crates/team-agent/src/model/paths.rs +28 -7
- package/crates/team-agent/src/model/permissions.rs +176 -33
- package/crates/team-agent/src/model/routing.rs +66 -20
- package/crates/team-agent/src/model/spec.rs +365 -69
- package/crates/team-agent/src/model/task_graph.rs +36 -9
- package/crates/team-agent/src/model/yaml/tests.rs +24 -6
- package/crates/team-agent/src/model/yaml.rs +7 -6
- package/crates/team-agent/src/packaging/install.rs +23 -9
- package/crates/team-agent/src/packaging/migrate.rs +5 -7
- package/crates/team-agent/src/packaging/mod.rs +9 -1
- package/crates/team-agent/src/packaging/repair.rs +13 -6
- package/crates/team-agent/src/packaging/tests.rs +63 -16
- package/crates/team-agent/src/packaging/types.rs +22 -7
- package/crates/team-agent/src/platform/argv.rs +4 -1
- package/crates/team-agent/src/platform/file_lock.rs +22 -8
- package/crates/team-agent/src/platform/process.rs +21 -22
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
- package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
- package/crates/team-agent/src/provider/classify.rs +127 -42
- package/crates/team-agent/src/provider/faults.rs +17 -5
- package/crates/team-agent/src/provider/helpers.rs +6 -5
- package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
- package/crates/team-agent/src/state/repository.rs +27 -14
- package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
- package/crates/team-agent/src/tmux_backend.rs +80 -44
- package/crates/team-agent/src/topology.rs +27 -20
- package/crates/team-agent/src/transport/test_support.rs +20 -23
- package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
- package/crates/team-agent/src/transport/tests/mod.rs +178 -187
- package/crates/team-agent/src/transport/tests/wire.rs +561 -525
- package/crates/team-agent/src/transport.rs +12 -17
- package/crates/team-agent/src/transport_factory.rs +29 -14
- package/package.json +4 -4
|
@@ -1,392 +1,464 @@
|
|
|
1
1
|
use super::*;
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
// =====================================================================
|
|
4
|
+
// 12. leader_identity_context — override / state uuid / derive 三源(unimplemented → RED)
|
|
5
|
+
// =====================================================================
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
7
|
+
// 无 override env、无 state record → derive(machine, ws_abspath, user, team)。
|
|
8
|
+
// 现 unimplemented → 调用即 RED;锁住返回 LeaderIdentity 且 source==Derived。
|
|
9
|
+
#[test]
|
|
10
|
+
#[serial_test::serial(env)]
|
|
11
|
+
fn leader_identity_context_derives_when_no_override_no_state() {
|
|
12
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_lic_{}", std::process::id()));
|
|
13
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
14
|
+
// 空 state(无 team_owner/leader_receiver uuid)。
|
|
15
|
+
let state = serde_json::json!({});
|
|
16
|
+
let id = leader_identity_context(&ws, None, Some(&state)).unwrap();
|
|
17
|
+
// 无 override → source 为 Derived(leader plan 侧;__init__.py:206)。
|
|
18
|
+
assert_eq!(
|
|
19
|
+
id.leader_session_uuid_source,
|
|
20
|
+
LeaderSessionUuidSource::Derived
|
|
21
|
+
);
|
|
22
|
+
// uuid 是 32 hex(derive 形)。
|
|
23
|
+
assert_eq!(id.leader_session_uuid.as_str().len(), 32);
|
|
24
|
+
}
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
26
|
+
// leader_identity:CLI 直出 dict(__init__.py:355-369)。unimplemented → RED。
|
|
27
|
+
// 强化:uuid_prefix 必须 == leader_identity_context 派生 uuid 的前 12 hex(绑到真值,
|
|
28
|
+
// 而非任意 12 字符);整个 dict 的 machine_fingerprint/os_user/team_id/source 必须与 context 一致。
|
|
29
|
+
#[test]
|
|
30
|
+
#[serial_test::serial(env)]
|
|
31
|
+
fn leader_identity_dict_ties_prefix_and_fields_to_derived_context() {
|
|
32
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_lid_{}", std::process::id()));
|
|
33
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
34
|
+
// 空 state → context 走 derive(无 override/无 state uuid)。
|
|
35
|
+
let ctx = leader_identity_context(&ws, None, Some(&serde_json::json!({}))).unwrap();
|
|
36
|
+
let expected_prefix = &ctx.leader_session_uuid.as_str()[..12];
|
|
37
|
+
let v = leader_identity(&ws, None).unwrap();
|
|
38
|
+
assert_eq!(v["ok"], serde_json::json!(true));
|
|
39
|
+
// uuid_prefix 绑到派生真值的前 12 hex(错的 12 字符串会被抓)。
|
|
40
|
+
assert_eq!(v["uuid_prefix"].as_str().unwrap(), expected_prefix);
|
|
41
|
+
// 其余身份字段与 context 字节一致。
|
|
42
|
+
assert_eq!(
|
|
43
|
+
v["machine_fingerprint"].as_str().unwrap(),
|
|
44
|
+
ctx.machine_fingerprint
|
|
45
|
+
);
|
|
46
|
+
assert_eq!(v["os_user"].as_str().unwrap(), ctx.os_user);
|
|
47
|
+
assert_eq!(v["team_id"].as_str().unwrap(), ctx.team_id.as_str());
|
|
48
|
+
// source == 派生侧 "derived"(无 override → 不是 "override"/"env")。
|
|
49
|
+
assert_eq!(v["source"], serde_json::json!("derived"));
|
|
50
|
+
assert_eq!(
|
|
51
|
+
ctx.leader_session_uuid_source,
|
|
52
|
+
LeaderSessionUuidSource::Derived
|
|
53
|
+
);
|
|
54
|
+
// CLI 直出形态:current_pane_id / last_seen_at 在无 env/无 receiver 时为 null。
|
|
55
|
+
assert_eq!(v["last_seen_at"], serde_json::Value::Null);
|
|
56
|
+
}
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
// =====================================================================
|
|
59
|
+
// 13. leader_start_plan(unimplemented → RED):钉 mode 选择 + leader_env 导出键。
|
|
60
|
+
// =====================================================================
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
62
|
+
// leader_start_plan(__init__.py:82-145)。强化:钉具体 mode + plan 内容,而非 provider 回声。
|
|
63
|
+
// 确定性环境:在 TMUX 内 → exec_provider;不在 TMUX(且 tmux 可用)→ new_tmux_session,
|
|
64
|
+
// session_name==leader_session_name(Fake,ws),leader_env 携带 5 个 TEAM_AGENT_* 导出键。
|
|
65
|
+
// 注:`detached` 在 leader_start_plan 返回值里恒为 false(__init__.py:174 "detached": False);
|
|
66
|
+
// 非 tty 的 `-d` 插入发生在 start_leader 调用者层(:74-78),不在本 plan 边界 → 不在此断言 detached。
|
|
67
|
+
// unimplemented → RED。
|
|
68
|
+
#[test]
|
|
69
|
+
#[serial_test::serial(env)]
|
|
70
|
+
fn leader_start_plan_pins_mode_and_leader_env() {
|
|
71
|
+
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
72
|
+
let _e = EnvGuard::apply(&[("TMUX", None), ("TMUX_PANE", None)]);
|
|
73
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_lsp_{}", std::process::id()));
|
|
74
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
75
|
+
let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false).unwrap();
|
|
76
|
+
assert_eq!(plan.provider, Provider::Fake);
|
|
77
|
+
assert_eq!(plan.mode, LeaderStartMode::ManagedTmuxClient);
|
|
78
|
+
// 0.3.28 Step 2: managed mode now uses the dedicated leader session
|
|
79
|
+
// (`team-agent-leader-<provider>-<folder>-<sha1[:8]>`), not the worker
|
|
80
|
+
// session `team-<team_id>`. Python parity.
|
|
81
|
+
let session_name = plan
|
|
82
|
+
.session_name
|
|
83
|
+
.as_ref()
|
|
84
|
+
.map(SessionName::as_str)
|
|
85
|
+
.unwrap_or("");
|
|
86
|
+
assert!(
|
|
87
|
+
session_name.starts_with(crate::layout::sessions::LEADER_SESSION_PREFIX),
|
|
88
|
+
"managed mode must use dedicated leader session prefix; got `{session_name}`"
|
|
89
|
+
);
|
|
90
|
+
// 0.3.28 Step 2: window name = provider wire (`fake`), not `leader`.
|
|
91
|
+
assert_eq!(
|
|
92
|
+
plan.leader_window.as_ref().map(WindowName::as_str),
|
|
93
|
+
Some("fake")
|
|
94
|
+
);
|
|
95
|
+
assert!(!plan.is_external_leader);
|
|
96
|
+
assert!(
|
|
97
|
+
plan.argv.iter().any(|arg| arg == "attach-session"),
|
|
98
|
+
"no-tmux managed launch attaches the user client to the leader window: {:?}",
|
|
99
|
+
plan.argv
|
|
100
|
+
);
|
|
101
|
+
// plan 边界 detached 恒 false(`-d` 插入在 start_leader 层,非此处)。
|
|
102
|
+
assert!(!plan.detached, "leader_start_plan 返回值 detached 恒 false");
|
|
103
|
+
// leader_env 携带 5 个 TEAM_AGENT_* 导出键(_leader_provider_env)。
|
|
104
|
+
for key in [
|
|
105
|
+
"TEAM_AGENT_LEADER_PROVIDER",
|
|
106
|
+
"TEAM_AGENT_LEADER_SESSION_UUID",
|
|
107
|
+
"TEAM_AGENT_MACHINE_FINGERPRINT",
|
|
108
|
+
"TEAM_AGENT_WORKSPACE",
|
|
109
|
+
"TEAM_AGENT_TEAM_ID",
|
|
110
|
+
] {
|
|
83
111
|
assert!(
|
|
84
|
-
plan.
|
|
85
|
-
"
|
|
86
|
-
plan.argv
|
|
87
|
-
);
|
|
88
|
-
// plan 边界 detached 恒 false(`-d` 插入在 start_leader 层,非此处)。
|
|
89
|
-
assert!(!plan.detached, "leader_start_plan 返回值 detached 恒 false");
|
|
90
|
-
// leader_env 携带 5 个 TEAM_AGENT_* 导出键(_leader_provider_env)。
|
|
91
|
-
for key in [
|
|
92
|
-
"TEAM_AGENT_LEADER_PROVIDER",
|
|
93
|
-
"TEAM_AGENT_LEADER_SESSION_UUID",
|
|
94
|
-
"TEAM_AGENT_MACHINE_FINGERPRINT",
|
|
95
|
-
"TEAM_AGENT_WORKSPACE",
|
|
96
|
-
"TEAM_AGENT_TEAM_ID",
|
|
97
|
-
] {
|
|
98
|
-
assert!(plan.leader_env.contains_key(key), "leader_env 缺导出键 {key}");
|
|
99
|
-
}
|
|
100
|
-
assert_eq!(
|
|
101
|
-
plan.leader_env.get("TEAM_AGENT_LEADER_PROVIDER").map(String::as_str),
|
|
102
|
-
Some("fake")
|
|
112
|
+
plan.leader_env.contains_key(key),
|
|
113
|
+
"leader_env 缺导出键 {key}"
|
|
103
114
|
);
|
|
104
115
|
}
|
|
116
|
+
assert_eq!(
|
|
117
|
+
plan.leader_env
|
|
118
|
+
.get("TEAM_AGENT_LEADER_PROVIDER")
|
|
119
|
+
.map(String::as_str),
|
|
120
|
+
Some("fake")
|
|
121
|
+
);
|
|
122
|
+
}
|
|
105
123
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
#[test]
|
|
125
|
+
#[serial_test::serial(env)]
|
|
126
|
+
fn leader_start_plan_external_leader_keeps_exec_provider_in_tmux() {
|
|
127
|
+
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
128
|
+
let _e = EnvGuard::apply(&[("TMUX", Some("/private/tmp/tmux-501/default,88432,187"))]);
|
|
129
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_lsp_external_{}", std::process::id()));
|
|
130
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
113
131
|
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
let provider_args = vec!["--".to_string(), "--model".to_string(), "opus".to_string()];
|
|
133
|
+
let plan = leader_start_plan(
|
|
134
|
+
Provider::Fake,
|
|
135
|
+
&provider_args,
|
|
136
|
+
&ws,
|
|
137
|
+
false,
|
|
138
|
+
false,
|
|
139
|
+
None,
|
|
140
|
+
true,
|
|
141
|
+
)
|
|
142
|
+
.unwrap();
|
|
116
143
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
144
|
+
assert_eq!(plan.mode, LeaderStartMode::ExecProvider);
|
|
145
|
+
assert!(plan.is_external_leader);
|
|
146
|
+
assert_eq!(plan.leader_window, None);
|
|
147
|
+
assert_eq!(
|
|
148
|
+
plan.argv,
|
|
149
|
+
vec![
|
|
150
|
+
"fake".to_string(),
|
|
151
|
+
"--model".to_string(),
|
|
152
|
+
"opus".to_string()
|
|
153
|
+
]
|
|
154
|
+
);
|
|
155
|
+
assert_eq!(
|
|
156
|
+
plan.provider_argv,
|
|
157
|
+
vec![
|
|
158
|
+
"fake".to_string(),
|
|
159
|
+
"--model".to_string(),
|
|
160
|
+
"opus".to_string()
|
|
161
|
+
]
|
|
162
|
+
);
|
|
163
|
+
}
|
|
129
164
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
165
|
+
#[test]
|
|
166
|
+
#[serial_test::serial(env)]
|
|
167
|
+
fn managed_leader_uses_dedicated_leader_session_independent_of_state_session_name() {
|
|
168
|
+
// 0.3.28 Step 2 amendment: the persisted `session_name` is the WORKER
|
|
169
|
+
// session, not the leader session. Managed leader now ignores it and
|
|
170
|
+
// always uses the dedicated `leader_session_name(provider, workspace)`.
|
|
171
|
+
// This test pins the new behaviour and the regression guard that the
|
|
172
|
+
// managed argv never gets a `team-team-*` double prefix.
|
|
173
|
+
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
174
|
+
let _e = EnvGuard::apply(&[("TMUX", None), ("TMUX_PANE", None)]);
|
|
175
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_lsp_existing_{}", std::process::id()));
|
|
176
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
177
|
+
crate::state::persist::save_runtime_state(
|
|
178
|
+
&ws,
|
|
179
|
+
&serde_json::json!({"session_name": "team-alpha"}),
|
|
180
|
+
)
|
|
181
|
+
.unwrap();
|
|
147
182
|
|
|
148
|
-
|
|
183
|
+
let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false).unwrap();
|
|
149
184
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
185
|
+
assert_eq!(plan.mode, LeaderStartMode::ManagedTmuxClient);
|
|
186
|
+
let session_name = plan
|
|
187
|
+
.session_name
|
|
188
|
+
.as_ref()
|
|
189
|
+
.map(SessionName::as_str)
|
|
190
|
+
.unwrap_or("");
|
|
191
|
+
assert!(
|
|
192
|
+
session_name.starts_with(crate::layout::sessions::LEADER_SESSION_PREFIX),
|
|
193
|
+
"managed mode uses dedicated leader session prefix regardless of \
|
|
155
194
|
persisted worker session_name; got `{session_name}`"
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
195
|
+
);
|
|
196
|
+
assert!(
|
|
197
|
+
!plan.argv.iter().any(|arg| arg.contains("team-team-alpha")),
|
|
198
|
+
"managed session name must not gain a second team- prefix: {:?}",
|
|
199
|
+
plan.argv
|
|
200
|
+
);
|
|
201
|
+
}
|
|
163
202
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
203
|
+
#[test]
|
|
204
|
+
#[serial_test::serial(env)]
|
|
205
|
+
fn in_tmux_default_leader_runs_provider_in_current_pane() {
|
|
206
|
+
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
207
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_lsp_switch_{}", std::process::id()));
|
|
208
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
209
|
+
let socket = crate::tmux_backend::socket_name_for_workspace(&ws);
|
|
210
|
+
let endpoint = format!("/private/tmp/tmux-501/{socket},88432,187");
|
|
211
|
+
let _e = EnvGuard::apply(&[("TMUX", Some(&endpoint)), ("TMUX_PANE", Some("%7"))]);
|
|
173
212
|
|
|
174
|
-
|
|
213
|
+
let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false).unwrap();
|
|
175
214
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
215
|
+
assert_eq!(plan.mode, LeaderStartMode::ExecProvider);
|
|
216
|
+
assert!(!plan.is_external_leader);
|
|
217
|
+
assert!(plan.session_name.is_none());
|
|
218
|
+
assert_eq!(plan.leader_window, None);
|
|
219
|
+
assert_eq!(plan.argv, vec!["fake".to_string()]);
|
|
220
|
+
assert!(
|
|
221
|
+
!plan
|
|
222
|
+
.argv
|
|
223
|
+
.iter()
|
|
224
|
+
.any(|arg| arg == "switch-client" || arg == "attach-session"),
|
|
225
|
+
"in-tmux default launch must not create or attach a background leader session: {:?}",
|
|
226
|
+
plan.argv
|
|
227
|
+
);
|
|
228
|
+
}
|
|
187
229
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
230
|
+
#[test]
|
|
231
|
+
#[serial_test::serial(env)]
|
|
232
|
+
fn in_tmux_default_leader_does_not_refuse_different_tmux_server() {
|
|
233
|
+
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
234
|
+
let _e = EnvGuard::apply(&[
|
|
235
|
+
("TMUX", Some("/private/tmp/tmux-501/default,88432,187")),
|
|
236
|
+
("TMUX_PANE", Some("%9")),
|
|
237
|
+
]);
|
|
238
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_lsp_refuse_{}", std::process::id()));
|
|
239
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
198
240
|
|
|
199
|
-
|
|
241
|
+
let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false).unwrap();
|
|
200
242
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
243
|
+
assert_eq!(plan.mode, LeaderStartMode::ExecProvider);
|
|
244
|
+
assert!(!plan.is_external_leader);
|
|
245
|
+
assert!(plan.session_name.is_none());
|
|
246
|
+
assert_eq!(plan.argv, vec!["fake".to_string()]);
|
|
247
|
+
}
|
|
206
248
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
249
|
+
#[test]
|
|
250
|
+
#[serial_test::serial(env)]
|
|
251
|
+
fn copilot_leader_env_disables_terminal_title_only_for_copilot() {
|
|
252
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_lsp_copilot_{}", std::process::id()));
|
|
253
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
254
|
+
let identity = leader_identity_context(&ws, None, Some(&serde_json::json!({}))).unwrap();
|
|
213
255
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
256
|
+
let copilot = leader_env_for_identity(Provider::Copilot, &identity);
|
|
257
|
+
assert_eq!(
|
|
258
|
+
copilot
|
|
259
|
+
.get("COPILOT_DISABLE_TERMINAL_TITLE")
|
|
260
|
+
.map(String::as_str),
|
|
261
|
+
Some("1")
|
|
262
|
+
);
|
|
219
263
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
264
|
+
for provider in [Provider::Codex, Provider::ClaudeCode] {
|
|
265
|
+
let leader_env = leader_env_for_identity(provider, &identity);
|
|
266
|
+
assert!(
|
|
267
|
+
!leader_env.contains_key("COPILOT_DISABLE_TERMINAL_TITLE"),
|
|
268
|
+
"{provider:?} leader env must not include copilot title override"
|
|
269
|
+
);
|
|
227
270
|
}
|
|
271
|
+
}
|
|
228
272
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
273
|
+
// ═══════════════ P2 FIX-LOOP RED (复绿即对抗 cross-model findings) ═══════════════
|
|
274
|
+
// Lock CORRECT Python v0.2.11 leader-identity behavior the contracts missed.
|
|
275
|
+
// Golden re-probed via /tmp/probe_p2_leader.py vs team-agent-public @ 439bef8
|
|
276
|
+
// (leader/__init__.py:_leader_identity_context / _identity_* / _detect_dual_state_divergence).
|
|
233
277
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
278
|
+
#[test]
|
|
279
|
+
#[serial_test::serial(env)]
|
|
280
|
+
fn p2_leader_state_uuid_source_is_derived_not_env() {
|
|
281
|
+
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
282
|
+
let _e = EnvGuard::apply(&[
|
|
283
|
+
("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None),
|
|
284
|
+
("TEAM_AGENT_LEADER_SESSION_UUID", None),
|
|
285
|
+
]);
|
|
286
|
+
let ws = p2_temp_ws("src");
|
|
287
|
+
let state = serde_json::json!({"team_owner": {"leader_session_uuid": "STATEUUID123"}});
|
|
288
|
+
let id = leader_identity_context(&ws, None, Some(&state)).unwrap();
|
|
289
|
+
assert_eq!(
|
|
290
|
+
id.leader_session_uuid_source,
|
|
291
|
+
LeaderSessionUuidSource::Derived
|
|
292
|
+
);
|
|
293
|
+
assert_eq!(id.leader_session_uuid.as_str(), "STATEUUID123");
|
|
294
|
+
}
|
|
248
295
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
296
|
+
// P1 — operator override env var is TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE (the
|
|
297
|
+
// _OVERRIDE suffix), per leader/__init__.py:197 — NOT the injected child-env var.
|
|
298
|
+
#[test]
|
|
299
|
+
#[serial_test::serial(env)]
|
|
300
|
+
fn p2_leader_override_reads_override_suffixed_env_var() {
|
|
301
|
+
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
302
|
+
let _e = EnvGuard::apply(&[
|
|
303
|
+
(
|
|
304
|
+
"TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE",
|
|
305
|
+
Some("OVERRIDE_X"),
|
|
306
|
+
),
|
|
307
|
+
("TEAM_AGENT_LEADER_SESSION_UUID", None),
|
|
308
|
+
]);
|
|
309
|
+
let ws = p2_temp_ws("ovr");
|
|
310
|
+
let id = leader_identity_context(&ws, None, None).unwrap();
|
|
311
|
+
assert_eq!(
|
|
312
|
+
id.leader_session_uuid_source,
|
|
313
|
+
LeaderSessionUuidSource::Override
|
|
314
|
+
);
|
|
315
|
+
assert_eq!(id.leader_session_uuid.as_str(), "OVERRIDE_X");
|
|
316
|
+
}
|
|
264
317
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
318
|
+
// P1 — derived inputs read state: machine_fingerprint = state team_owner record first
|
|
319
|
+
// (_identity_machine_fingerprint); team_id = team_state_key(state) from session_name
|
|
320
|
+
// (default 'current', not a hardcoded 'default').
|
|
321
|
+
#[test]
|
|
322
|
+
#[serial_test::serial(env)]
|
|
323
|
+
fn p2_leader_derived_inputs_read_state_record() {
|
|
324
|
+
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
325
|
+
let _e = EnvGuard::apply(&[("TEAM_AGENT_MACHINE_FINGERPRINT", None)]);
|
|
326
|
+
let ws = p2_temp_ws("der");
|
|
327
|
+
let state = serde_json::json!({
|
|
328
|
+
"team_owner": {"machine_fingerprint": "RECORDED-FP-FROM-STATE"},
|
|
329
|
+
"session_name": "team-agent-myteam"
|
|
330
|
+
});
|
|
331
|
+
let id = leader_identity_context(&ws, None, Some(&state)).unwrap();
|
|
332
|
+
assert_eq!(
|
|
333
|
+
id.machine_fingerprint, "RECORDED-FP-FROM-STATE",
|
|
334
|
+
"state record fp beats env/hostname"
|
|
335
|
+
);
|
|
336
|
+
assert_eq!(
|
|
337
|
+
id.team_id.as_str(),
|
|
338
|
+
"team-agent-myteam",
|
|
339
|
+
"team_id from state.session_name"
|
|
340
|
+
);
|
|
341
|
+
}
|
|
282
342
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
343
|
+
// P1 — os_user fallback chain = USER or USERNAME or '' (_identity_os_user), NOT
|
|
344
|
+
// USER or LOGNAME or 'unknown'. (USERNAME is the 2nd choice; empty-string fallback.)
|
|
345
|
+
#[test]
|
|
346
|
+
#[serial_test::serial(env)]
|
|
347
|
+
fn p2_leader_os_user_honors_username_then_empty() {
|
|
348
|
+
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
349
|
+
let _e = EnvGuard::apply(&[
|
|
350
|
+
("USER", None),
|
|
351
|
+
("LOGNAME", None),
|
|
352
|
+
("USERNAME", Some("winuser")),
|
|
353
|
+
]);
|
|
354
|
+
let ws = p2_temp_ws("usr");
|
|
355
|
+
let id = leader_identity_context(&ws, None, None).unwrap();
|
|
356
|
+
assert_eq!(
|
|
357
|
+
id.os_user, "winuser",
|
|
358
|
+
"USERNAME is the second choice (not LOGNAME)"
|
|
359
|
+
);
|
|
360
|
+
}
|
|
298
361
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
362
|
+
// P1 — detect_dual_state_divergence must catch an owner leader_session_uuid split even
|
|
363
|
+
// when panes + epoch are identical (leader/__init__.py:574).
|
|
364
|
+
#[test]
|
|
365
|
+
#[serial_test::serial(env)]
|
|
366
|
+
fn p2_leader_detect_divergence_catches_owner_uuid_split() {
|
|
367
|
+
let ws = p2_temp_ws("div");
|
|
368
|
+
let snap_dir = crate::model::paths::runtime_dir(&ws)
|
|
369
|
+
.join("teams")
|
|
370
|
+
.join("sess1");
|
|
371
|
+
std::fs::create_dir_all(&snap_dir).unwrap();
|
|
372
|
+
let snap = serde_json::json!({
|
|
373
|
+
"session_name":"sess1",
|
|
374
|
+
"team_owner":{"pane_id":"%1","leader_session_uuid":"UUID_B","owner_epoch":5},
|
|
375
|
+
"leader_receiver":{"pane_id":"%1"}
|
|
376
|
+
});
|
|
377
|
+
std::fs::write(
|
|
378
|
+
snap_dir.join("state.json"),
|
|
379
|
+
serde_json::to_string(&snap).unwrap(),
|
|
380
|
+
)
|
|
381
|
+
.unwrap();
|
|
382
|
+
let state = serde_json::json!({
|
|
383
|
+
"session_name":"sess1",
|
|
384
|
+
"team_owner":{"pane_id":"%1","leader_session_uuid":"UUID_A","owner_epoch":5},
|
|
385
|
+
"leader_receiver":{"pane_id":"%1"}
|
|
386
|
+
});
|
|
387
|
+
let div = detect_dual_state_divergence(&ws, &state).unwrap();
|
|
388
|
+
assert!(
|
|
389
|
+
div.is_some(),
|
|
390
|
+
"owner uuid split (A vs B) with matching panes/epoch must be detected"
|
|
391
|
+
);
|
|
392
|
+
}
|
|
321
393
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
394
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
395
|
+
// 14. WAVE-2 Lane B CONTRACT PASS — CLI-handler-facing byte-parity for the
|
|
396
|
+
// three verbs (claim-leader / takeover / identity) + their core lease
|
|
397
|
+
// machinery (_claim_lease_no_incident outcomes / _lease_refused shapes).
|
|
398
|
+
//
|
|
399
|
+
// GOLDEN (re-probed @ team-agent-public, leader/__init__.py +
|
|
400
|
+
// runtime.py:721/791). Each test labels RED|LOCK honestly:
|
|
401
|
+
// RED = drives an unimplemented!() body (claim_lease_no_incident /
|
|
402
|
+
// attach_leader_to_state) → panics today = correct RED-first.
|
|
403
|
+
// LOCK = drives an already-implemented stub/path → green today; pins
|
|
404
|
+
// the golden so a future port cannot regress it.
|
|
405
|
+
// Deferred to later adversarial rounds (Lane-A style): the ambiguous-
|
|
406
|
+
// incident claim arm (no_caller_pane / caller_not_candidate / dry_run /
|
|
407
|
+
// lost_race) which needs a seeded event-log incident + the broadcast
|
|
408
|
+
// requeue cross-lane; the strict-uuid attach refusal string (needs a
|
|
409
|
+
// live pane resolver). Those are #[ignore]/NOTE seams below.
|
|
410
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
339
411
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
412
|
+
// ── 14a. _claim_lease_no_incident OUTCOMES (golden __init__.py:598) ──────
|
|
413
|
+
//
|
|
414
|
+
// claim_lease_no_incident is unimplemented!() → every test here PANICS today
|
|
415
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
416
|
+
// unit-0 (Stage 0) characterization tests
|
|
417
|
+
//
|
|
418
|
+
// Pin two invariants that unit-1/3/4 will refactor behind typed
|
|
419
|
+
// identity helpers. If the constant or the prefix shape changes, the
|
|
420
|
+
// refactor must also update these tests.
|
|
421
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
350
422
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
423
|
+
#[test]
|
|
424
|
+
fn unit0_leader_session_prefix_constant_is_stable() {
|
|
425
|
+
// Pinned: the literal leader session prefix that gates "is this a
|
|
426
|
+
// leader session" decisions everywhere in the runtime.
|
|
427
|
+
assert_eq!(
|
|
428
|
+
crate::leader::start::LEADER_SESSION_PREFIX,
|
|
429
|
+
"team-agent-leader-"
|
|
430
|
+
);
|
|
431
|
+
// The layout module re-exports the same value (kept in sync via
|
|
432
|
+
// const re-export).
|
|
433
|
+
assert_eq!(
|
|
434
|
+
crate::layout::sessions::LEADER_SESSION_PREFIX,
|
|
435
|
+
crate::leader::start::LEADER_SESSION_PREFIX
|
|
436
|
+
);
|
|
437
|
+
}
|
|
366
438
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
439
|
+
#[test]
|
|
440
|
+
fn unit0_leader_prefixed_name_must_never_be_taken_as_worker_session() {
|
|
441
|
+
// Pinned invariant fed into unit-1 typed identity: any session
|
|
442
|
+
// name starting with `LEADER_SESSION_PREFIX` is a leader launcher
|
|
443
|
+
// session, never a worker session. unit-1 will wrap this rule in
|
|
444
|
+
// typed constructors (`WorkerSession::new` rejects the prefix,
|
|
445
|
+
// `LeaderLauncherSession::new` requires it).
|
|
446
|
+
let leader = "team-agent-leader-claude-x-aaaaaaaaaaaa";
|
|
447
|
+
let worker = "team-real-worker";
|
|
448
|
+
let prefix = crate::leader::start::LEADER_SESSION_PREFIX;
|
|
449
|
+
assert!(leader.starts_with(prefix));
|
|
450
|
+
assert!(!worker.starts_with(prefix));
|
|
451
|
+
// Symmetry: the runtime today uses this exact check
|
|
452
|
+
// (cli/mod.rs:261 `starts_with(LEADER_SESSION_PREFIX)`) to spare
|
|
453
|
+
// leader sessions during shutdown.
|
|
454
|
+
let leader_name = crate::transport::SessionName::new(leader);
|
|
455
|
+
let worker_name = crate::transport::SessionName::new(worker);
|
|
456
|
+
assert!(
|
|
457
|
+
crate::layout::sessions::is_leader_session(&leader_name),
|
|
458
|
+
"is_leader_session must accept a leader-prefixed session",
|
|
459
|
+
);
|
|
460
|
+
assert!(
|
|
461
|
+
!crate::layout::sessions::is_leader_session(&worker_name),
|
|
462
|
+
"is_leader_session must reject a non-prefixed session",
|
|
463
|
+
);
|
|
464
|
+
}
|