@team-agent/installer 0.4.11 → 0.5.1
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 -0
- package/crates/team-agent/src/cli/diagnose.rs +2 -15
- package/crates/team-agent/src/cli/emit.rs +98 -5
- package/crates/team-agent/src/cli/mod.rs +2 -0
- package/crates/team-agent/src/cli/named_address.rs +864 -0
- package/crates/team-agent/src/cli/send.rs +104 -0
- package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -0
- package/crates/team-agent/src/cli/tests/mod.rs +1 -0
- package/crates/team-agent/src/cli/tests/named_address.rs +455 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +1 -0
- package/crates/team-agent/src/cli/types.rs +4 -0
- package/crates/team-agent/src/compiler.rs +11 -20
- package/crates/team-agent/src/coordinator/orphan.rs +2 -2
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +5 -4
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +1776 -2
- package/crates/team-agent/src/coordinator/steps/mod.rs +19 -0
- package/crates/team-agent/src/coordinator/tests/a0_lostupdate.rs +78 -7
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +195 -0
- package/crates/team-agent/src/coordinator/tick.rs +31 -932
- package/crates/team-agent/src/diagnose/orphans.rs +66 -8
- package/crates/team-agent/src/layout/worker_window_helpers.rs +5 -7
- package/crates/team-agent/src/leader/provider_attribution.rs +7 -5
- package/crates/team-agent/src/leader/rediscover.rs +1 -11
- package/crates/team-agent/src/lifecycle/launch/plan.rs +19 -8
- package/crates/team-agent/src/lifecycle/launch.rs +135 -58
- package/crates/team-agent/src/lifecycle/lock.rs +302 -0
- package/crates/team-agent/src/lifecycle/mod.rs +1 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +1 -11
- package/crates/team-agent/src/lifecycle/restart/agent.rs +169 -113
- package/crates/team-agent/src/lifecycle/restart/common.rs +108 -17
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +165 -32
- package/crates/team-agent/src/lifecycle/restart/remove.rs +16 -2
- package/crates/team-agent/src/lifecycle/restart/selection.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +6 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +3 -3
- package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +321 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +35 -5
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +802 -0
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +675 -0
- package/crates/team-agent/src/lifecycle/tests.rs +3 -0
- package/crates/team-agent/src/lifecycle/types.rs +8 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +24 -9
- package/crates/team-agent/src/mcp_server/tools.rs +157 -68
- package/crates/team-agent/src/messaging/activity.rs +43 -18
- package/crates/team-agent/src/messaging/delivery.rs +161 -97
- package/crates/team-agent/src/messaging/helpers.rs +1 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +22 -1
- package/crates/team-agent/src/messaging/results.rs +34 -9
- package/crates/team-agent/src/messaging/scheduler.rs +52 -9
- package/crates/team-agent/src/model/spec.rs +10 -6
- package/crates/team-agent/src/provider/adapter.rs +10 -1038
- package/crates/team-agent/src/provider/adapters/claude.rs +3 -8
- package/crates/team-agent/src/provider/adapters/copilot.rs +2 -5
- package/crates/team-agent/src/provider/classify.rs +23 -48
- package/crates/team-agent/src/provider/command.rs +16 -7
- package/crates/team-agent/src/provider/faults.rs +93 -53
- package/crates/team-agent/src/provider/session/capture.rs +4 -15
- package/crates/team-agent/src/provider/session_scan/claude.rs +309 -0
- package/crates/team-agent/src/provider/session_scan/codex.rs +40 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +350 -0
- package/crates/team-agent/src/provider/session_scan/copilot.rs +202 -0
- package/crates/team-agent/src/provider/session_scan.rs +65 -27
- package/crates/team-agent/src/provider/tests/faults.rs +80 -0
- package/crates/team-agent/src/provider/types.rs +33 -1
- package/crates/team-agent/src/provider/wire.rs +171 -1
- package/crates/team-agent/src/state/identity.rs +242 -57
- package/crates/team-agent/src/state/identity_keys.rs +9 -12
- package/crates/team-agent/src/state/mod.rs +5 -1
- package/crates/team-agent/src/state/owner_gate.rs +59 -15
- package/crates/team-agent/src/state/ownership.rs +12 -11
- package/crates/team-agent/src/state/paths.rs +13 -6
- package/crates/team-agent/src/state/persist.rs +671 -128
- package/crates/team-agent/src/state/projection.rs +240 -49
- package/crates/team-agent/src/state/selector.rs +11 -5
- package/crates/team-agent/src/tmux_backend/tests.rs +51 -2
- package/crates/team-agent/src/tmux_backend.rs +42 -5
- package/crates/team-agent/src/transport/test_support.rs +55 -6
- package/package.json +4 -4
|
@@ -59,7 +59,10 @@ pub fn check_team_owner(
|
|
|
59
59
|
if !owner.is_object() || owner.as_object().is_none_or(serde_json::Map::is_empty) {
|
|
60
60
|
return None;
|
|
61
61
|
}
|
|
62
|
-
let owner_uuid = owner
|
|
62
|
+
let owner_uuid = owner
|
|
63
|
+
.get("leader_session_uuid")
|
|
64
|
+
.and_then(Value::as_str)
|
|
65
|
+
.unwrap_or("");
|
|
63
66
|
let owner_pane = owner.get("pane_id").and_then(Value::as_str).unwrap_or("");
|
|
64
67
|
let caller_uuid = caller.leader_session_uuid.as_str();
|
|
65
68
|
let caller_pane = caller.pane_id.as_str();
|
|
@@ -178,7 +181,9 @@ fn lexical_abs(p: &Path) -> std::path::PathBuf {
|
|
|
178
181
|
if p.is_absolute() {
|
|
179
182
|
p.to_path_buf()
|
|
180
183
|
} else {
|
|
181
|
-
std::env::current_dir()
|
|
184
|
+
std::env::current_dir()
|
|
185
|
+
.map(|cwd| cwd.join(p))
|
|
186
|
+
.unwrap_or_else(|_| p.to_path_buf())
|
|
182
187
|
}
|
|
183
188
|
}
|
|
184
189
|
|
|
@@ -253,7 +258,9 @@ mod tests {
|
|
|
253
258
|
#[test]
|
|
254
259
|
fn no_owner_allows() {
|
|
255
260
|
assert!(check_team_owner(&json!({}), &caller("%c", "u"), true, &LIVE).is_none());
|
|
256
|
-
assert!(
|
|
261
|
+
assert!(
|
|
262
|
+
check_team_owner(&json!({"team_owner": {}}), &caller("%c", "u"), true, &LIVE).is_none()
|
|
263
|
+
);
|
|
257
264
|
}
|
|
258
265
|
|
|
259
266
|
#[test]
|
|
@@ -298,15 +305,30 @@ mod tests {
|
|
|
298
305
|
fn worker_bypass_rules() {
|
|
299
306
|
let s = json!({"leader": {"id": "leader"}, "agents": {"worker_a": {}, "worker_b": {}}});
|
|
300
307
|
// worker_a + env worker_a → 绕过。
|
|
301
|
-
assert_eq!(
|
|
308
|
+
assert_eq!(
|
|
309
|
+
worker_sender_bypasses_owner_gate(&s, Some("worker_a"), Some("worker_a")),
|
|
310
|
+
Some("worker_a".to_string())
|
|
311
|
+
);
|
|
302
312
|
// leader → 不绕过(走 owner 门)。
|
|
303
|
-
assert_eq!(
|
|
313
|
+
assert_eq!(
|
|
314
|
+
worker_sender_bypasses_owner_gate(&s, Some("leader"), Some("leader")),
|
|
315
|
+
None
|
|
316
|
+
);
|
|
304
317
|
// 未注册 agent → 不绕过。
|
|
305
|
-
assert_eq!(
|
|
318
|
+
assert_eq!(
|
|
319
|
+
worker_sender_bypasses_owner_gate(&s, Some("ghost"), Some("ghost")),
|
|
320
|
+
None
|
|
321
|
+
);
|
|
306
322
|
// sender/env 不一致(伪造)→ 不绕过。
|
|
307
|
-
assert_eq!(
|
|
323
|
+
assert_eq!(
|
|
324
|
+
worker_sender_bypasses_owner_gate(&s, Some("worker_b"), Some("worker_a")),
|
|
325
|
+
None
|
|
326
|
+
);
|
|
308
327
|
// 无 env_agent_id + 已注册 → 绕过(返回 sender)。
|
|
309
|
-
assert_eq!(
|
|
328
|
+
assert_eq!(
|
|
329
|
+
worker_sender_bypasses_owner_gate(&s, Some("worker_a"), None),
|
|
330
|
+
Some("worker_a".to_string())
|
|
331
|
+
);
|
|
310
332
|
}
|
|
311
333
|
|
|
312
334
|
// §11 血泪:/tmp → /private/tmp 软链不对称(macOS 真机 bug)。两边 canonicalize → match。
|
|
@@ -319,7 +341,10 @@ mod tests {
|
|
|
319
341
|
let raw = Path::new("/tmp").join(dir.file_name().unwrap());
|
|
320
342
|
let private = Path::new("/private/tmp").join(dir.file_name().unwrap());
|
|
321
343
|
if raw.exists() && private.exists() {
|
|
322
|
-
assert!(
|
|
344
|
+
assert!(
|
|
345
|
+
workspace_paths_match(&raw, &private),
|
|
346
|
+
"/tmp 与 /private/tmp 应 canonicalize 相等"
|
|
347
|
+
);
|
|
323
348
|
}
|
|
324
349
|
// 自反:同路径 match。
|
|
325
350
|
assert!(workspace_paths_match(&dir, &dir));
|
|
@@ -333,7 +358,10 @@ mod tests {
|
|
|
333
358
|
let repo_backup = base.join("repo-backup");
|
|
334
359
|
std::fs::create_dir_all(&repo).unwrap();
|
|
335
360
|
std::fs::create_dir_all(&repo_backup).unwrap();
|
|
336
|
-
assert!(
|
|
361
|
+
assert!(
|
|
362
|
+
!workspace_paths_match(&repo, &repo_backup),
|
|
363
|
+
"共享前缀的兄弟目录不得 match"
|
|
364
|
+
);
|
|
337
365
|
// 子目录也不 match(禁子串/反推)。
|
|
338
366
|
let sub = repo.join("sub");
|
|
339
367
|
std::fs::create_dir_all(&sub).unwrap();
|
|
@@ -359,15 +387,25 @@ mod tests {
|
|
|
359
387
|
c.leader_session_uuid_source = "derived".to_string();
|
|
360
388
|
let r = check_team_owner(&s, &c, true, &LIVE);
|
|
361
389
|
assert!(r.is_some(), "两边空 uuid 不得 allow;必落拒绝");
|
|
362
|
-
assert_eq!(
|
|
390
|
+
assert_eq!(
|
|
391
|
+
r.unwrap()["reason_kind"],
|
|
392
|
+
json!("owner_takeover_required"),
|
|
393
|
+
"非 sticky_bind(空 uuid 不算同)"
|
|
394
|
+
);
|
|
363
395
|
}
|
|
364
396
|
|
|
365
397
|
// 对抗 P2:owner pane liveness == Unknown 时,非 worker caller 也接管;worker 不接管。
|
|
366
398
|
#[test]
|
|
367
399
|
fn unknown_owner_pane_allows_nonworker_takeover() {
|
|
368
400
|
let s = state_owned("%owner", "uuid-owner");
|
|
369
|
-
assert!(
|
|
370
|
-
|
|
401
|
+
assert!(
|
|
402
|
+
check_team_owner(&s, &caller("%new", "uuid-new"), false, &UNKNOWN).is_none(),
|
|
403
|
+
"Unknown owner pane + 非 worker → 接管"
|
|
404
|
+
);
|
|
405
|
+
assert!(
|
|
406
|
+
check_team_owner(&s, &caller("%new", "uuid-new"), true, &UNKNOWN).is_some(),
|
|
407
|
+
"worker 即便 Unknown 也不接管"
|
|
408
|
+
);
|
|
371
409
|
}
|
|
372
410
|
|
|
373
411
|
// 空 caller_pane + 异 uuid + 死 owner:死-owner 接管分支需 caller_pane 非空,故落拒绝。
|
|
@@ -375,7 +413,10 @@ mod tests {
|
|
|
375
413
|
fn empty_caller_pane_diff_uuid_refuses_even_dead_owner() {
|
|
376
414
|
let s = state_owned("%owner", "uuid-owner");
|
|
377
415
|
let r = check_team_owner(&s, &caller("", "uuid-diff"), false, &DEAD);
|
|
378
|
-
assert!(
|
|
416
|
+
assert!(
|
|
417
|
+
r.is_some(),
|
|
418
|
+
"空 caller_pane 不触发死-owner 接管;异 uuid → 拒绝"
|
|
419
|
+
);
|
|
379
420
|
}
|
|
380
421
|
|
|
381
422
|
// 对抗 P0-B(§11 血泪):父目录是软链 + 末段不存在 → realpath_like 解析父软链 → 判 own。
|
|
@@ -396,7 +437,10 @@ mod tests {
|
|
|
396
437
|
"父软链 + 缺失末段应判 own(realpath_like 解析父软链)"
|
|
397
438
|
);
|
|
398
439
|
// 不同末段仍不 match。
|
|
399
|
-
assert!(!workspace_paths_match(
|
|
440
|
+
assert!(!workspace_paths_match(
|
|
441
|
+
&link.join("ghostA"),
|
|
442
|
+
&real.join("ghostB")
|
|
443
|
+
));
|
|
400
444
|
}
|
|
401
445
|
}
|
|
402
446
|
|
|
@@ -164,13 +164,11 @@ pub fn write_owner(state: &mut Value, team_key: &str, record: OwnershipWrite) {
|
|
|
164
164
|
}
|
|
165
165
|
// Canonical teams.<key> write. Stage 5 will move this to a per-team
|
|
166
166
|
// state file; the call sites do not change.
|
|
167
|
-
let teams = state
|
|
168
|
-
.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
.as_object_mut()
|
|
173
|
-
});
|
|
167
|
+
let teams = state.as_object_mut().and_then(|root| {
|
|
168
|
+
root.entry("teams")
|
|
169
|
+
.or_insert_with(|| serde_json::json!({}))
|
|
170
|
+
.as_object_mut()
|
|
171
|
+
});
|
|
174
172
|
let Some(teams) = teams else { return };
|
|
175
173
|
let entry = teams
|
|
176
174
|
.entry(team_key.to_string())
|
|
@@ -402,7 +400,10 @@ mod tests {
|
|
|
402
400
|
assert!(state.get("owner_epoch").is_none());
|
|
403
401
|
assert!(state.get("leader_receiver").is_none());
|
|
404
402
|
// Canonical teams.<key>: written.
|
|
405
|
-
assert_eq!(
|
|
403
|
+
assert_eq!(
|
|
404
|
+
state["teams"]["alpha"]["team_owner"]["pane_id"],
|
|
405
|
+
json!("%new")
|
|
406
|
+
);
|
|
406
407
|
assert_eq!(state["teams"]["alpha"]["owner_epoch"], json!(7));
|
|
407
408
|
assert_eq!(
|
|
408
409
|
state["teams"]["alpha"]["leader_receiver"]["pane_id"],
|
|
@@ -442,9 +443,9 @@ mod tests {
|
|
|
442
443
|
write_owner(&mut state, "", record);
|
|
443
444
|
assert_eq!(state["team_owner"]["pane_id"], json!("%top"));
|
|
444
445
|
assert!(
|
|
445
|
-
state
|
|
446
|
-
.
|
|
447
|
-
.is_some_and(|map| map.is_empty())),
|
|
446
|
+
state
|
|
447
|
+
.get("teams")
|
|
448
|
+
.is_none_or(|teams| teams.as_object().is_some_and(|map| map.is_empty())),
|
|
448
449
|
"empty team_key must NOT touch teams.<key>"
|
|
449
450
|
);
|
|
450
451
|
}
|
|
@@ -96,7 +96,10 @@ impl TeamRuntimePaths {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
pub fn for_scope(scope: &TeamScope) -> Self {
|
|
99
|
-
Self::new(
|
|
99
|
+
Self::new(
|
|
100
|
+
scope.workspace().to_path_buf(),
|
|
101
|
+
scope.team_key().to_string(),
|
|
102
|
+
)
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
pub fn workspace(&self) -> &Path {
|
|
@@ -188,9 +191,7 @@ impl CommandScope {
|
|
|
188
191
|
let alive = team_state_candidates(&state);
|
|
189
192
|
match alive.len() {
|
|
190
193
|
0 => Self::EmptyWorkspace,
|
|
191
|
-
1 => Self::Resolved(
|
|
192
|
-
alive.keys().next().cloned().unwrap_or_default(),
|
|
193
|
-
),
|
|
194
|
+
1 => Self::Resolved(alive.keys().next().cloned().unwrap_or_default()),
|
|
194
195
|
_ => {
|
|
195
196
|
let mut keys: Vec<String> = alive.keys().cloned().collect();
|
|
196
197
|
keys.sort();
|
|
@@ -241,7 +242,10 @@ mod tests {
|
|
|
241
242
|
#[test]
|
|
242
243
|
fn team_runtime_paths_layout_matches_existing_runtime_dir_layout() {
|
|
243
244
|
let paths = TeamRuntimePaths::new(PathBuf::from("/ws/proj"), "alpha");
|
|
244
|
-
assert_eq!(
|
|
245
|
+
assert_eq!(
|
|
246
|
+
paths.team_dir(),
|
|
247
|
+
PathBuf::from("/ws/proj/.team/runtime/alpha")
|
|
248
|
+
);
|
|
245
249
|
assert_eq!(
|
|
246
250
|
paths.spec_path(),
|
|
247
251
|
PathBuf::from("/ws/proj/.team/runtime/alpha/team.spec.yaml")
|
|
@@ -338,7 +342,10 @@ mod tests {
|
|
|
338
342
|
.unwrap();
|
|
339
343
|
let scope = CommandScope::resolve(&ws, None);
|
|
340
344
|
assert!(scope.is_ambiguous());
|
|
341
|
-
assert_eq!(
|
|
345
|
+
assert_eq!(
|
|
346
|
+
scope.candidates(),
|
|
347
|
+
&["alpha".to_string(), "beta".to_string()]
|
|
348
|
+
);
|
|
342
349
|
}
|
|
343
350
|
|
|
344
351
|
#[test]
|