@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,217 +1,243 @@
|
|
|
1
1
|
use super::*;
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
// =====================================================================
|
|
4
|
+
// 4. wake 层纯函数(unimplemented → RED)。golden:probe_leader.py 5 分支 + boundary。
|
|
5
|
+
// =====================================================================
|
|
6
|
+
|
|
7
|
+
#[test]
|
|
8
|
+
fn should_reread_no_file_when_current_mtime_none() {
|
|
9
|
+
// current_mtime is None → {reread:false, no_file}(wake.py:31-32)。
|
|
10
|
+
let d = should_reread(None, None, None, 100.0, 60.0);
|
|
11
|
+
assert!(!d.reread);
|
|
12
|
+
assert_eq!(d.reason, RereadReason::NoFile);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#[test]
|
|
16
|
+
fn should_reread_never_classified() {
|
|
17
|
+
// last_classified_mtime is None → {reread:true, never_classified}。
|
|
18
|
+
let d = should_reread(None, Some(10.0), None, 100.0, 60.0);
|
|
19
|
+
assert!(d.reread);
|
|
20
|
+
assert_eq!(d.reason, RereadReason::NeverClassified);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[test]
|
|
24
|
+
fn should_reread_file_changed() {
|
|
25
|
+
// current != last_classified → {reread:true, file_changed}。
|
|
26
|
+
let d = should_reread(Some(5.0), Some(20.0), Some(10.0), 100.0, 60.0);
|
|
27
|
+
assert!(d.reread);
|
|
28
|
+
assert_eq!(d.reason, RereadReason::FileChanged);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#[test]
|
|
32
|
+
fn should_reread_quiescent_already_classified_at_and_past_debounce() {
|
|
33
|
+
// current==last_classified 且 silent_for >= debounce → quiescent(不再重读)。
|
|
34
|
+
// silent_for = max(0, now-current_mtime) = 100-10 = 90 >= 60。
|
|
35
|
+
let d = should_reread(Some(10.0), Some(10.0), Some(10.0), 100.0, 60.0);
|
|
36
|
+
assert!(!d.reread);
|
|
37
|
+
assert_eq!(d.reason, RereadReason::QuiescentAlreadyClassified);
|
|
38
|
+
// 边界:silent_for 恰 == debounce(now-current=60)→ 仍 quiescent(>= 比较)。
|
|
39
|
+
let b = should_reread(Some(40.0), Some(40.0), Some(40.0), 100.0, 60.0);
|
|
40
|
+
assert!(!b.reread);
|
|
41
|
+
assert_eq!(b.reason, RereadReason::QuiescentAlreadyClassified);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[test]
|
|
45
|
+
fn should_reread_unchanged_within_debounce() {
|
|
46
|
+
// current==last_classified 且 silent_for < debounce → unchanged。
|
|
47
|
+
// now-current = 100-95 = 5 < 60。注:last_mtime 在 Python body 中未被使用。
|
|
48
|
+
let d = should_reread(Some(10.0), Some(95.0), Some(95.0), 100.0, 60.0);
|
|
49
|
+
assert!(!d.reread);
|
|
50
|
+
assert_eq!(d.reason, RereadReason::Unchanged);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#[test]
|
|
54
|
+
fn on_file_changed_adds_node_sorted_and_records_mtime() {
|
|
55
|
+
// golden probe_leader.py:add b → pending ["b"];add a → sorted ["a","b"]。
|
|
56
|
+
let s0 = on_file_changed(None, "b", 1.0);
|
|
57
|
+
assert_eq!(s0.pending, vec!["b".to_string()]);
|
|
58
|
+
assert_eq!(s0.mtimes.get("b"), Some(&1.0));
|
|
59
|
+
let s1 = on_file_changed(Some(&s0), "a", 2.0);
|
|
60
|
+
assert_eq!(
|
|
61
|
+
s1.pending,
|
|
62
|
+
vec!["a".to_string(), "b".to_string()],
|
|
63
|
+
"pending 必须 sorted"
|
|
64
|
+
);
|
|
65
|
+
assert_eq!(s1.mtimes.get("a"), Some(&2.0));
|
|
66
|
+
// 重复 add b:pending 仍是 set(不重复),mtime 被更新到 3.0。
|
|
67
|
+
let s2 = on_file_changed(Some(&s1), "b", 3.0);
|
|
68
|
+
assert_eq!(s2.pending, vec!["a".to_string(), "b".to_string()]);
|
|
69
|
+
assert_eq!(s2.mtimes.get("b"), Some(&3.0));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#[test]
|
|
73
|
+
fn take_pending_drains_sorted_and_clears_pending_but_keeps_mtimes() {
|
|
74
|
+
// golden probe_leader.py:drain → (["a","b"], state{pending:[], mtimes 保留})。
|
|
75
|
+
let mut st = on_file_changed(None, "b", 3.0);
|
|
76
|
+
st = on_file_changed(Some(&st), "a", 2.0);
|
|
77
|
+
let (drained, after) = take_pending(Some(&st));
|
|
78
|
+
assert_eq!(drained, vec!["a".to_string(), "b".to_string()]);
|
|
79
|
+
assert!(after.pending.is_empty());
|
|
80
|
+
// mtimes 不被 drain 清空。
|
|
81
|
+
assert_eq!(after.mtimes.get("a"), Some(&2.0));
|
|
82
|
+
assert_eq!(after.mtimes.get("b"), Some(&3.0));
|
|
83
|
+
// 再 drain → 空。
|
|
84
|
+
let (drained2, _after2) = take_pending(Some(&after));
|
|
85
|
+
assert!(drained2.is_empty());
|
|
86
|
+
// drain None → ([], default state)。
|
|
87
|
+
let (none_drained, none_state) = take_pending(None);
|
|
88
|
+
assert!(none_drained.is_empty());
|
|
89
|
+
assert!(none_state.pending.is_empty());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// =====================================================================
|
|
93
|
+
// 5. leader_session_name — sha1 派生 + 文件夹消毒(unimplemented → RED)
|
|
94
|
+
// =====================================================================
|
|
95
|
+
|
|
96
|
+
// 公式:team-agent-leader-<provider>-<sanitized folder[:48]>-<sha1(resolve(ws))[:8]>。
|
|
97
|
+
// 用真实 temp 目录,sha1/sanitize 在测试内复算后断言函数输出与之一致(probe 已验证公式)。
|
|
98
|
+
#[test]
|
|
99
|
+
fn leader_session_name_formula_and_sanitization() {
|
|
100
|
+
// 公式 = team-agent-leader-<provider>-<sanitized folder>-<sha1(resolve(ws))[:8]>。
|
|
101
|
+
// sha1 复算需 sha1 crate(本测试不引);改为断言格式不变量(provider/消毒/8-hex 后缀),
|
|
102
|
+
// 字节级 sha1 由 golden probe_leader.py 已验证公式正确。
|
|
103
|
+
let base = std::env::temp_dir().join(format!("ta_rs_lsn_{}", std::process::id()));
|
|
104
|
+
let weird = base.join("My Proj!@#name");
|
|
105
|
+
std::fs::create_dir_all(&weird).unwrap();
|
|
106
|
+
let got = leader_session_name(Provider::Codex, &weird);
|
|
107
|
+
// 前缀 + provider + 消毒后的 folder(非字母数字_.- → '_')。
|
|
108
|
+
let s = got.as_str();
|
|
109
|
+
assert!(
|
|
110
|
+
s.starts_with("team-agent-leader-codex-My_Proj___name-"),
|
|
111
|
+
"got {s}"
|
|
112
|
+
);
|
|
113
|
+
// sha1[:8] 后缀:8 个 hex。
|
|
114
|
+
let suffix = s.rsplit('-').next().unwrap();
|
|
115
|
+
assert_eq!(suffix.len(), 8, "sha1 前缀须 8 hex,got {suffix}");
|
|
116
|
+
assert!(suffix.chars().all(|c| c.is_ascii_hexdigit()));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// folder 名消毒成空 → 回退 "workspace"(probe_leader.py allsym 用例)。
|
|
120
|
+
#[test]
|
|
121
|
+
fn leader_session_name_empty_sanitized_folder_falls_back_to_workspace() {
|
|
122
|
+
let base = std::env::temp_dir().join(format!("ta_rs_lsn2_{}", std::process::id()));
|
|
123
|
+
// 全符号目录名 → 消毒后 strip('._-') 为空 → "workspace"。
|
|
124
|
+
let allsym = base.join("@@@");
|
|
125
|
+
std::fs::create_dir_all(&allsym).unwrap();
|
|
126
|
+
let got = leader_session_name(Provider::Codex, &allsym);
|
|
127
|
+
assert!(
|
|
128
|
+
got.as_str().contains("-workspace-"),
|
|
129
|
+
"全符号 folder 应回退 'workspace',got {}",
|
|
130
|
+
got.as_str()
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// claude_code provider 出现在 session 名里(probe:team-agent-leader-claude_code-...)。
|
|
135
|
+
#[test]
|
|
136
|
+
fn leader_session_name_uses_claude_code_provider_string() {
|
|
137
|
+
let base = std::env::temp_dir().join(format!("ta_rs_lsn3_{}", std::process::id()));
|
|
138
|
+
let dir = base.join("proj");
|
|
139
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
140
|
+
let got = leader_session_name(Provider::ClaudeCode, &dir);
|
|
141
|
+
assert!(
|
|
142
|
+
got.as_str()
|
|
143
|
+
.starts_with("team-agent-leader-claude_code-proj-"),
|
|
144
|
+
"got {}",
|
|
145
|
+
got.as_str()
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#[test]
|
|
150
|
+
fn leader_session_name_uses_copilot_provider_string() {
|
|
151
|
+
let base = std::env::temp_dir().join(format!("ta_rs_lsn_copilot_{}", std::process::id()));
|
|
152
|
+
let dir = base.join("proj");
|
|
153
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
154
|
+
let got = leader_session_name(Provider::Copilot, &dir);
|
|
155
|
+
assert!(
|
|
156
|
+
got.as_str().starts_with("team-agent-leader-copilot-proj-"),
|
|
157
|
+
"got {}",
|
|
158
|
+
got.as_str()
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// =====================================================================
|
|
163
|
+
// 6. Family A 正源 owner 绑定 — bind_owner_from_caller_pane(unimplemented → RED)
|
|
164
|
+
// =====================================================================
|
|
165
|
+
|
|
166
|
+
// $TMUX_PANE 缺 → refuse + reason=caller_pane_missing(leader_binding.py:79-95)。
|
|
167
|
+
// 此处只能在 $TMUX_PANE 缺失环境下断言(测试进程通常无 TMUX_PANE)。
|
|
168
|
+
#[test]
|
|
169
|
+
fn bind_owner_refuses_when_caller_pane_missing() {
|
|
170
|
+
// 防御:确保本测试看到的环境无 TMUX_PANE(若 CI 在 tmux 内跑,跳过断言形态)。
|
|
171
|
+
if std::env::var_os("TMUX_PANE").is_some() {
|
|
172
|
+
// 在 tmux 内:正源存在,不该走 refuse 分支;此用例只验缺失分支,直接返回。
|
|
173
|
+
return;
|
|
13
174
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
fn should_reread_file_changed() {
|
|
25
|
-
// current != last_classified → {reread:true, file_changed}。
|
|
26
|
-
let d = should_reread(Some(5.0), Some(20.0), Some(10.0), 100.0, 60.0);
|
|
27
|
-
assert!(d.reread);
|
|
28
|
-
assert_eq!(d.reason, RereadReason::FileChanged);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
#[test]
|
|
32
|
-
fn should_reread_quiescent_already_classified_at_and_past_debounce() {
|
|
33
|
-
// current==last_classified 且 silent_for >= debounce → quiescent(不再重读)。
|
|
34
|
-
// silent_for = max(0, now-current_mtime) = 100-10 = 90 >= 60。
|
|
35
|
-
let d = should_reread(Some(10.0), Some(10.0), Some(10.0), 100.0, 60.0);
|
|
36
|
-
assert!(!d.reread);
|
|
37
|
-
assert_eq!(d.reason, RereadReason::QuiescentAlreadyClassified);
|
|
38
|
-
// 边界:silent_for 恰 == debounce(now-current=60)→ 仍 quiescent(>= 比较)。
|
|
39
|
-
let b = should_reread(Some(40.0), Some(40.0), Some(40.0), 100.0, 60.0);
|
|
40
|
-
assert!(!b.reread);
|
|
41
|
-
assert_eq!(b.reason, RereadReason::QuiescentAlreadyClassified);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
#[test]
|
|
45
|
-
fn should_reread_unchanged_within_debounce() {
|
|
46
|
-
// current==last_classified 且 silent_for < debounce → unchanged。
|
|
47
|
-
// now-current = 100-95 = 5 < 60。注:last_mtime 在 Python body 中未被使用。
|
|
48
|
-
let d = should_reread(Some(10.0), Some(95.0), Some(95.0), 100.0, 60.0);
|
|
49
|
-
assert!(!d.reread);
|
|
50
|
-
assert_eq!(d.reason, RereadReason::Unchanged);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
#[test]
|
|
54
|
-
fn on_file_changed_adds_node_sorted_and_records_mtime() {
|
|
55
|
-
// golden probe_leader.py:add b → pending ["b"];add a → sorted ["a","b"]。
|
|
56
|
-
let s0 = on_file_changed(None, "b", 1.0);
|
|
57
|
-
assert_eq!(s0.pending, vec!["b".to_string()]);
|
|
58
|
-
assert_eq!(s0.mtimes.get("b"), Some(&1.0));
|
|
59
|
-
let s1 = on_file_changed(Some(&s0), "a", 2.0);
|
|
60
|
-
assert_eq!(s1.pending, vec!["a".to_string(), "b".to_string()], "pending 必须 sorted");
|
|
61
|
-
assert_eq!(s1.mtimes.get("a"), Some(&2.0));
|
|
62
|
-
// 重复 add b:pending 仍是 set(不重复),mtime 被更新到 3.0。
|
|
63
|
-
let s2 = on_file_changed(Some(&s1), "b", 3.0);
|
|
64
|
-
assert_eq!(s2.pending, vec!["a".to_string(), "b".to_string()]);
|
|
65
|
-
assert_eq!(s2.mtimes.get("b"), Some(&3.0));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
#[test]
|
|
69
|
-
fn take_pending_drains_sorted_and_clears_pending_but_keeps_mtimes() {
|
|
70
|
-
// golden probe_leader.py:drain → (["a","b"], state{pending:[], mtimes 保留})。
|
|
71
|
-
let mut st = on_file_changed(None, "b", 3.0);
|
|
72
|
-
st = on_file_changed(Some(&st), "a", 2.0);
|
|
73
|
-
let (drained, after) = take_pending(Some(&st));
|
|
74
|
-
assert_eq!(drained, vec!["a".to_string(), "b".to_string()]);
|
|
75
|
-
assert!(after.pending.is_empty());
|
|
76
|
-
// mtimes 不被 drain 清空。
|
|
77
|
-
assert_eq!(after.mtimes.get("a"), Some(&2.0));
|
|
78
|
-
assert_eq!(after.mtimes.get("b"), Some(&3.0));
|
|
79
|
-
// 再 drain → 空。
|
|
80
|
-
let (drained2, _after2) = take_pending(Some(&after));
|
|
81
|
-
assert!(drained2.is_empty());
|
|
82
|
-
// drain None → ([], default state)。
|
|
83
|
-
let (none_drained, none_state) = take_pending(None);
|
|
84
|
-
assert!(none_drained.is_empty());
|
|
85
|
-
assert!(none_state.pending.is_empty());
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// =====================================================================
|
|
89
|
-
// 5. leader_session_name — sha1 派生 + 文件夹消毒(unimplemented → RED)
|
|
90
|
-
// =====================================================================
|
|
91
|
-
|
|
92
|
-
// 公式:team-agent-leader-<provider>-<sanitized folder[:48]>-<sha1(resolve(ws))[:8]>。
|
|
93
|
-
// 用真实 temp 目录,sha1/sanitize 在测试内复算后断言函数输出与之一致(probe 已验证公式)。
|
|
94
|
-
#[test]
|
|
95
|
-
fn leader_session_name_formula_and_sanitization() {
|
|
96
|
-
// 公式 = team-agent-leader-<provider>-<sanitized folder>-<sha1(resolve(ws))[:8]>。
|
|
97
|
-
// sha1 复算需 sha1 crate(本测试不引);改为断言格式不变量(provider/消毒/8-hex 后缀),
|
|
98
|
-
// 字节级 sha1 由 golden probe_leader.py 已验证公式正确。
|
|
99
|
-
let base = std::env::temp_dir().join(format!("ta_rs_lsn_{}", std::process::id()));
|
|
100
|
-
let weird = base.join("My Proj!@#name");
|
|
101
|
-
std::fs::create_dir_all(&weird).unwrap();
|
|
102
|
-
let got = leader_session_name(Provider::Codex, &weird);
|
|
103
|
-
// 前缀 + provider + 消毒后的 folder(非字母数字_.- → '_')。
|
|
104
|
-
let s = got.as_str();
|
|
105
|
-
assert!(s.starts_with("team-agent-leader-codex-My_Proj___name-"), "got {s}");
|
|
106
|
-
// sha1[:8] 后缀:8 个 hex。
|
|
107
|
-
let suffix = s.rsplit('-').next().unwrap();
|
|
108
|
-
assert_eq!(suffix.len(), 8, "sha1 前缀须 8 hex,got {suffix}");
|
|
109
|
-
assert!(suffix.chars().all(|c| c.is_ascii_hexdigit()));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// folder 名消毒成空 → 回退 "workspace"(probe_leader.py allsym 用例)。
|
|
113
|
-
#[test]
|
|
114
|
-
fn leader_session_name_empty_sanitized_folder_falls_back_to_workspace() {
|
|
115
|
-
let base = std::env::temp_dir().join(format!("ta_rs_lsn2_{}", std::process::id()));
|
|
116
|
-
// 全符号目录名 → 消毒后 strip('._-') 为空 → "workspace"。
|
|
117
|
-
let allsym = base.join("@@@");
|
|
118
|
-
std::fs::create_dir_all(&allsym).unwrap();
|
|
119
|
-
let got = leader_session_name(Provider::Codex, &allsym);
|
|
120
|
-
assert!(
|
|
121
|
-
got.as_str().contains("-workspace-"),
|
|
122
|
-
"全符号 folder 应回退 'workspace',got {}",
|
|
123
|
-
got.as_str()
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// claude_code provider 出现在 session 名里(probe:team-agent-leader-claude_code-...)。
|
|
128
|
-
#[test]
|
|
129
|
-
fn leader_session_name_uses_claude_code_provider_string() {
|
|
130
|
-
let base = std::env::temp_dir().join(format!("ta_rs_lsn3_{}", std::process::id()));
|
|
131
|
-
let dir = base.join("proj");
|
|
132
|
-
std::fs::create_dir_all(&dir).unwrap();
|
|
133
|
-
let got = leader_session_name(Provider::ClaudeCode, &dir);
|
|
134
|
-
assert!(got.as_str().starts_with("team-agent-leader-claude_code-proj-"), "got {}", got.as_str());
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
#[test]
|
|
138
|
-
fn leader_session_name_uses_copilot_provider_string() {
|
|
139
|
-
let base = std::env::temp_dir().join(format!("ta_rs_lsn_copilot_{}", std::process::id()));
|
|
140
|
-
let dir = base.join("proj");
|
|
141
|
-
std::fs::create_dir_all(&dir).unwrap();
|
|
142
|
-
let got = leader_session_name(Provider::Copilot, &dir);
|
|
143
|
-
assert!(
|
|
144
|
-
got.as_str().starts_with("team-agent-leader-copilot-proj-"),
|
|
145
|
-
"got {}",
|
|
146
|
-
got.as_str()
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// =====================================================================
|
|
151
|
-
// 6. Family A 正源 owner 绑定 — bind_owner_from_caller_pane(unimplemented → RED)
|
|
152
|
-
// =====================================================================
|
|
153
|
-
|
|
154
|
-
// $TMUX_PANE 缺 → refuse + reason=caller_pane_missing(leader_binding.py:79-95)。
|
|
155
|
-
// 此处只能在 $TMUX_PANE 缺失环境下断言(测试进程通常无 TMUX_PANE)。
|
|
156
|
-
#[test]
|
|
157
|
-
fn bind_owner_refuses_when_caller_pane_missing() {
|
|
158
|
-
// 防御:确保本测试看到的环境无 TMUX_PANE(若 CI 在 tmux 内跑,跳过断言形态)。
|
|
159
|
-
if std::env::var_os("TMUX_PANE").is_some() {
|
|
160
|
-
// 在 tmux 内:正源存在,不该走 refuse 分支;此用例只验缺失分支,直接返回。
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
let ws = std::env::temp_dir().join(format!("ta_rs_bind_{}", std::process::id()));
|
|
164
|
-
std::fs::create_dir_all(&ws).unwrap();
|
|
165
|
-
let team = TeamKey::new("default");
|
|
166
|
-
let res = bind_owner_from_caller_pane(&ws, &team, None).unwrap();
|
|
167
|
-
assert!(!res.ok);
|
|
168
|
-
assert_eq!(res.reason, Some(LeaseReason::CallerPaneMissing));
|
|
169
|
-
// caller_pane_id 为空(probe:""),hint 为 _HINT_RUN_FROM_LEADER_PANE。
|
|
170
|
-
assert_eq!(res.caller_pane_id, PaneId::new(""));
|
|
171
|
-
assert_eq!(res.caller_current_command, "");
|
|
172
|
-
assert_eq!(
|
|
175
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_bind_{}", std::process::id()));
|
|
176
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
177
|
+
let team = TeamKey::new("default");
|
|
178
|
+
let res = bind_owner_from_caller_pane(&ws, &team, None).unwrap();
|
|
179
|
+
assert!(!res.ok);
|
|
180
|
+
assert_eq!(res.reason, Some(LeaseReason::CallerPaneMissing));
|
|
181
|
+
// caller_pane_id 为空(probe:""),hint 为 _HINT_RUN_FROM_LEADER_PANE。
|
|
182
|
+
assert_eq!(res.caller_pane_id, PaneId::new(""));
|
|
183
|
+
assert_eq!(res.caller_current_command, "");
|
|
184
|
+
assert_eq!(
|
|
173
185
|
res.hint.as_deref(),
|
|
174
186
|
Some("run team-agent from inside your leader pane (the tmux pane you want to own this team).")
|
|
175
187
|
);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
188
|
+
assert_eq!(res.team_id, team);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// owner.bind_refused 事件名字节锁(LeaderEvent::name unimplemented → RED;与 #5 重叠但锁 binding 路径)。
|
|
192
|
+
#[test]
|
|
193
|
+
fn owner_bind_refused_event_name_is_owner_bind_refused() {
|
|
194
|
+
assert_eq!(LeaderEvent::OwnerBindRefused.name(), "owner.bind_refused");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// emit_owner_bound_event:成功绑定 hook(owner.bound_from_caller_pane;leader_binding.py:162-183)。
|
|
198
|
+
// 强化(no-full-uuid-leak 命门):事件只写 derived_uuid_prefix == derived[:12](12 hex),
|
|
199
|
+
// old uuid 为 None → old_uuid_prefix == ""(空串,非缺省);全 32 hex uuid 绝不出现在任何字段。
|
|
200
|
+
// unimplemented → RED。
|
|
201
|
+
#[test]
|
|
202
|
+
fn emit_owner_bound_event_logs_prefix_only_never_full_uuid() {
|
|
203
|
+
let ws = std::env::temp_dir().join(format!("ta_rs_emit_{}", std::process::id()));
|
|
204
|
+
std::fs::create_dir_all(&ws).unwrap();
|
|
205
|
+
let caller = PaneId::new("%7");
|
|
206
|
+
let derived = uuid("fp", "/ws", "u", "default");
|
|
207
|
+
let full = derived.as_str().to_string();
|
|
208
|
+
assert_eq!(full.len(), 32, "derive 产 32 hex");
|
|
209
|
+
let prefix12 = full[..12].to_string();
|
|
210
|
+
emit_owner_bound_event(
|
|
211
|
+
&ws,
|
|
212
|
+
&caller,
|
|
213
|
+
"codex",
|
|
214
|
+
&derived,
|
|
215
|
+
&TeamKey::new("default"),
|
|
216
|
+
None,
|
|
217
|
+
)
|
|
218
|
+
.unwrap();
|
|
219
|
+
// 读回审计事件:恰一条 owner.bound_from_caller_pane。
|
|
220
|
+
let events = crate::event_log::EventLog::new(&ws).tail(50).unwrap();
|
|
221
|
+
let ev = events
|
|
222
|
+
.iter()
|
|
223
|
+
.find(|e| e["event"] == serde_json::json!("owner.bound_from_caller_pane"))
|
|
224
|
+
.expect("必写 owner.bound_from_caller_pane");
|
|
225
|
+
assert_eq!(ev["caller_pane_id"], serde_json::json!("%7"));
|
|
226
|
+
assert_eq!(ev["caller_current_command"], serde_json::json!("codex"));
|
|
227
|
+
assert_eq!(ev["team_id"], serde_json::json!("default"));
|
|
228
|
+
// derived_uuid_prefix == derived[:12](只前缀,12 hex)。
|
|
229
|
+
assert_eq!(ev["derived_uuid_prefix"], serde_json::json!(prefix12));
|
|
230
|
+
// old uuid=None → old_uuid_prefix == ""(空串,非 null/缺省;golden probe 已验)。
|
|
231
|
+
assert_eq!(ev["old_uuid_prefix"], serde_json::json!(""));
|
|
232
|
+
// no-full-uuid-leak:整条事件序列化文本里绝不出现完整 32-hex uuid。
|
|
233
|
+
let raw = serde_json::to_string(ev).unwrap();
|
|
234
|
+
assert!(
|
|
235
|
+
!raw.contains(&full),
|
|
236
|
+
"审计事件绝不泄露完整 leader_session_uuid"
|
|
237
|
+
);
|
|
238
|
+
// 审计事件名字节锁。
|
|
239
|
+
assert_eq!(
|
|
240
|
+
LeaderEvent::OwnerBoundFromCallerPane.name(),
|
|
241
|
+
"owner.bound_from_caller_pane"
|
|
242
|
+
);
|
|
243
|
+
}
|
|
@@ -69,10 +69,10 @@ pub mod transport;
|
|
|
69
69
|
// step 10-12 (leader/messaging/coordinator) — ROUND-0.5 类型+fn surface 骨架(fn body unimplemented!(),
|
|
70
70
|
// P2 porter 落实现)。leader=lease/owner-bind/idle-takeover;messaging=send/deliver/retry/watchers;
|
|
71
71
|
// coordinator=daemon tick(§10 no-panic,Result<TickReport,TickError>)。
|
|
72
|
-
pub mod leader;
|
|
73
|
-
pub mod messaging;
|
|
74
72
|
pub mod coordinator;
|
|
75
73
|
pub mod diagnose;
|
|
74
|
+
pub mod leader;
|
|
75
|
+
pub mod messaging;
|
|
76
76
|
|
|
77
77
|
// step 13-15 (lifecycle/mcp_server/cli/packaging) — ROUND-0.5b behavioral-rich 骨架(entry-fn 签名 +
|
|
78
78
|
// 富返回类型,fn body unimplemented!(),P2 porter 落实现)。lifecycle=quick-start/restart/display;
|
|
@@ -87,11 +87,11 @@ pub mod lifecycle;
|
|
|
87
87
|
pub mod platform;
|
|
88
88
|
// 0.3.28 — unified adaptive layout manager (single source of truth for tmux
|
|
89
89
|
// topology decisions). See `.team/artifacts/adaptive-layout-full-architecture-locate.md`.
|
|
90
|
+
pub mod cli;
|
|
90
91
|
pub mod layout;
|
|
91
|
-
pub mod topology;
|
|
92
92
|
pub mod mcp_server;
|
|
93
|
-
pub mod cli;
|
|
94
93
|
pub mod packaging;
|
|
94
|
+
pub mod topology;
|
|
95
95
|
|
|
96
96
|
// fake-worker — subscription-free backing program for Provider::Fake; lets the real spawn path
|
|
97
97
|
// (launch dry_run=false → tmux window) be exercised with no real provider (port of fake_worker.py).
|
|
@@ -217,7 +217,10 @@ fn open_adaptive_worker_displays(
|
|
|
217
217
|
workspace_window: target.window.clone(),
|
|
218
218
|
pane_id: target.pane_id.clone(),
|
|
219
219
|
pane_title: Some(agent_id),
|
|
220
|
-
target: target
|
|
220
|
+
target: target
|
|
221
|
+
.pane_id
|
|
222
|
+
.as_ref()
|
|
223
|
+
.map(|pane| pane.as_str().to_string()),
|
|
221
224
|
target_worker_session: Some(session_name.as_str().to_string()),
|
|
222
225
|
linked_session: None,
|
|
223
226
|
leader_session: Some(session_name.clone()),
|
|
@@ -279,7 +282,9 @@ fn close_adaptive_displays(
|
|
|
279
282
|
}
|
|
280
283
|
}
|
|
281
284
|
if let Some(leader_session) = adaptive_leader_session(&state) {
|
|
282
|
-
for target in
|
|
285
|
+
for target in
|
|
286
|
+
close_adaptive_windows(&transport, leader_session.as_str(), session_name.as_str())
|
|
287
|
+
{
|
|
283
288
|
orphans_cleaned.push(target.clone());
|
|
284
289
|
if seen.insert(target.clone()) {
|
|
285
290
|
closed.push(target);
|
|
@@ -481,4 +486,3 @@ fn string_field(display: &serde_json::Map<String, serde_json::Value>, key: &str)
|
|
|
481
486
|
.filter(|s| !s.is_empty())
|
|
482
487
|
.map(str::to_string)
|
|
483
488
|
}
|
|
484
|
-
|
|
@@ -139,7 +139,12 @@ pub fn launch_with_transport_in_workspace(
|
|
|
139
139
|
.map(|id| id == agent.agent_id.as_str())
|
|
140
140
|
.unwrap_or(false)
|
|
141
141
|
})
|
|
142
|
-
.and_then(|entry|
|
|
142
|
+
.and_then(|entry| {
|
|
143
|
+
entry
|
|
144
|
+
.get("provider")
|
|
145
|
+
.and_then(Value::as_str)
|
|
146
|
+
.map(str::to_string)
|
|
147
|
+
})
|
|
143
148
|
.unwrap_or_else(|| "fake".to_string());
|
|
144
149
|
crate::lifecycle::restart::write_worker_spawn_timing_event(
|
|
145
150
|
workspace,
|
|
@@ -28,7 +28,15 @@
|
|
|
28
28
|
//! contracts blitz 可 NAME 这些类型并 CALL 这些 fn、断言其 rich return。
|
|
29
29
|
|
|
30
30
|
// ROUND-0 skeleton:fn body 全 unimplemented!() → import/field/param/大 Err 暂未落地;P2 porter 实现时移除。
|
|
31
|
-
#![allow(
|
|
31
|
+
#![allow(
|
|
32
|
+
dead_code,
|
|
33
|
+
unused_imports,
|
|
34
|
+
unused_variables,
|
|
35
|
+
clippy::result_large_err,
|
|
36
|
+
clippy::doc_overindented_list_items,
|
|
37
|
+
clippy::doc_lazy_continuation,
|
|
38
|
+
clippy::io_other_error
|
|
39
|
+
)]
|
|
32
40
|
// §10:lifecycle 写路径(save_runtime_state/snapshot/plan_state,bug-084 高危)实现层禁 unwrap/expect/panic
|
|
33
41
|
// (unimplemented!() stub 不被拦);tests 子模块各自 allow。
|
|
34
42
|
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|