@team-agent/installer 0.5.26 → 0.5.28
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
CHANGED
package/Cargo.toml
CHANGED
|
@@ -3463,9 +3463,16 @@ pub mod lifecycle_port {
|
|
|
3463
3463
|
out
|
|
3464
3464
|
}
|
|
3465
3465
|
|
|
3466
|
-
/// 0.5.26 (`.team/artifacts/stale-team-saveconflict-locate.md` §7.2)
|
|
3467
|
-
///
|
|
3466
|
+
/// 0.5.26 (`.team/artifacts/stale-team-saveconflict-locate.md` §7.2) /
|
|
3467
|
+
/// 0.5.27 (`.team/artifacts/0526-supermarket-shutdown-status-real-fail-locate.md` §5.1):
|
|
3468
|
+
/// scoped shutdown 只有一队,`state.active_team_key` 指向被关的那队;
|
|
3468
3469
|
/// 只在 session_killed && !verification_degraded 时被调用。
|
|
3470
|
+
///
|
|
3471
|
+
/// 同时 stamp 顶层 `state["status"]` 与 `teams[active_key]["status"]`:
|
|
3472
|
+
/// scoped 关队走 `save_team_scoped_state`,反写盘的源是 `compact(top-level)`
|
|
3473
|
+
/// (见 `state/persist.rs::save_team_scoped_state` +
|
|
3474
|
+
/// `projection::compact_team_state`),只标 nested 会被顶层视图 clobber 丢盘。
|
|
3475
|
+
/// 保留 nested 写入以维持同 tick 内的 in-memory 一致(其他 read-back 立即看到)。
|
|
3469
3476
|
fn mark_active_team_shutdown(state: &mut Value, status: &str) {
|
|
3470
3477
|
let Some(active_key) = state
|
|
3471
3478
|
.get("active_team_key")
|
|
@@ -3474,6 +3481,9 @@ pub mod lifecycle_port {
|
|
|
3474
3481
|
else {
|
|
3475
3482
|
return;
|
|
3476
3483
|
};
|
|
3484
|
+
if let Some(obj) = state.as_object_mut() {
|
|
3485
|
+
obj.insert("status".to_string(), json!(status));
|
|
3486
|
+
}
|
|
3477
3487
|
let Some(teams) = state.get_mut("teams").and_then(Value::as_object_mut) else {
|
|
3478
3488
|
return;
|
|
3479
3489
|
};
|
|
@@ -331,6 +331,129 @@ fn scoped_shutdown_keeps_owned_endpoint_when_sibling_session_remains() {
|
|
|
331
331
|
);
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
#[test]
|
|
335
|
+
fn scoped_clean_shutdown_persists_team_shutdown_status_after_disk_roundtrip() {
|
|
336
|
+
let ws = tmp_shutdown_workspace("scoped-clean-status-roundtrip");
|
|
337
|
+
crate::state::persist::save_runtime_state(
|
|
338
|
+
&ws,
|
|
339
|
+
&json!({
|
|
340
|
+
"schema_version": 1,
|
|
341
|
+
"active_team_key": "current",
|
|
342
|
+
"team_key": "current",
|
|
343
|
+
"session_name": "team-current",
|
|
344
|
+
"team_dir": ws.display().to_string(),
|
|
345
|
+
"workspace": ws.display().to_string(),
|
|
346
|
+
"agents": {
|
|
347
|
+
"adminweb": {
|
|
348
|
+
"agent_id": "adminweb",
|
|
349
|
+
"status": "running",
|
|
350
|
+
"provider": "fake",
|
|
351
|
+
"window": "adminweb"
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
"teams": {
|
|
355
|
+
"current": {
|
|
356
|
+
"team_key": "current",
|
|
357
|
+
"session_name": "team-current",
|
|
358
|
+
"team_dir": ws.display().to_string(),
|
|
359
|
+
"workspace": ws.display().to_string(),
|
|
360
|
+
"agents": {
|
|
361
|
+
"adminweb": {
|
|
362
|
+
"agent_id": "adminweb",
|
|
363
|
+
"status": "running",
|
|
364
|
+
"provider": "fake",
|
|
365
|
+
"window": "adminweb"
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}),
|
|
371
|
+
)
|
|
372
|
+
.unwrap();
|
|
373
|
+
|
|
374
|
+
let out = crate::cli::lifecycle_port::shutdown_with_transport(
|
|
375
|
+
&ws,
|
|
376
|
+
true,
|
|
377
|
+
Some("current"),
|
|
378
|
+
&CleanShutdownTransport::new(),
|
|
379
|
+
)
|
|
380
|
+
.expect("shutdown should complete");
|
|
381
|
+
|
|
382
|
+
assert_eq!(out["ok"], json!(true), "shutdown report: {out}");
|
|
383
|
+
assert_eq!(out["session_killed"], json!(true), "shutdown report: {out}");
|
|
384
|
+
assert_eq!(out["verification_degraded"], json!(false), "shutdown report: {out}");
|
|
385
|
+
let saved = crate::state::persist::load_runtime_state(&ws).unwrap();
|
|
386
|
+
assert_eq!(
|
|
387
|
+
saved.pointer("/teams/current/status").and_then(serde_json::Value::as_str),
|
|
388
|
+
Some("shutdown"),
|
|
389
|
+
"0.5.27 RED: clean scoped shutdown returned session_killed=true and not degraded, \
|
|
390
|
+
so disk state must persist teams.current.status=shutdown. The compact projected \
|
|
391
|
+
save path must not drop the tombstone; saved={saved}"
|
|
392
|
+
);
|
|
393
|
+
assert_eq!(
|
|
394
|
+
saved
|
|
395
|
+
.pointer("/teams/current/agents/adminweb/status")
|
|
396
|
+
.and_then(serde_json::Value::as_str),
|
|
397
|
+
Some("stopped"),
|
|
398
|
+
"0.5.27 RED: clean shutdown must also persist stopped agents under the retained team; saved={saved}"
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
#[test]
|
|
403
|
+
fn scoped_degraded_shutdown_does_not_persist_team_shutdown_status() {
|
|
404
|
+
let ws = tmp_shutdown_workspace("scoped-degraded-no-status");
|
|
405
|
+
crate::state::persist::save_runtime_state(
|
|
406
|
+
&ws,
|
|
407
|
+
&json!({
|
|
408
|
+
"schema_version": 1,
|
|
409
|
+
"active_team_key": "current",
|
|
410
|
+
"team_key": "current",
|
|
411
|
+
"session_name": "team-current",
|
|
412
|
+
"agents": {
|
|
413
|
+
"adminweb": {
|
|
414
|
+
"agent_id": "adminweb",
|
|
415
|
+
"status": "running",
|
|
416
|
+
"provider": "fake",
|
|
417
|
+
"window": "adminweb"
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
"teams": {
|
|
421
|
+
"current": {
|
|
422
|
+
"team_key": "current",
|
|
423
|
+
"session_name": "team-current",
|
|
424
|
+
"agents": {
|
|
425
|
+
"adminweb": {
|
|
426
|
+
"agent_id": "adminweb",
|
|
427
|
+
"status": "running",
|
|
428
|
+
"provider": "fake",
|
|
429
|
+
"window": "adminweb"
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}),
|
|
435
|
+
)
|
|
436
|
+
.unwrap();
|
|
437
|
+
|
|
438
|
+
let out = crate::cli::lifecycle_port::shutdown_with_transport(
|
|
439
|
+
&ws,
|
|
440
|
+
true,
|
|
441
|
+
Some("current"),
|
|
442
|
+
&CleanShutdownTransport::new().with_probe_timeout("ps_table"),
|
|
443
|
+
)
|
|
444
|
+
.expect("shutdown should complete");
|
|
445
|
+
|
|
446
|
+
assert_eq!(out["session_killed"], json!(true), "shutdown report: {out}");
|
|
447
|
+
assert_eq!(out["verification_degraded"], json!(true), "shutdown report: {out}");
|
|
448
|
+
let saved = crate::state::persist::load_runtime_state(&ws).unwrap();
|
|
449
|
+
assert_ne!(
|
|
450
|
+
saved.pointer("/teams/current/status").and_then(serde_json::Value::as_str),
|
|
451
|
+
Some("shutdown"),
|
|
452
|
+
"degraded shutdown must remain dirty/diagnostic and must not stamp \
|
|
453
|
+
teams.current.status=shutdown; saved={saved}"
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
|
|
334
457
|
#[test]
|
|
335
458
|
fn repeated_owned_endpoint_shutdowns_leave_no_socket_file_growth() {
|
|
336
459
|
let ws = tmp_shutdown_workspace("owned-loop-no-growth");
|
|
@@ -672,9 +672,16 @@ pub(super) fn sync_restart_team_projections(state: &mut serde_json::Value, team_
|
|
|
672
672
|
else {
|
|
673
673
|
return;
|
|
674
674
|
};
|
|
675
|
+
// 0.5.28 (`.team/artifacts/0527-realfail-layer2-leader-locate.md` §3):
|
|
676
|
+
// 显式 team_key = 操作目标,始终允许覆盖 / 新建(该 helper 的正当职责)。
|
|
677
|
+
// active/derived/"current" 三种别名兜底 = alias-identity 家族第三例,只有
|
|
678
|
+
// 「盘上已有该 alias 条目」且「其身份与 compact 不冲突」时才允许写,
|
|
679
|
+
// 避免 0.5.26 起死 sibling 保留时被活队 compact 硬克隆。禁止 alias 新建条目。
|
|
675
680
|
let mut keys = Vec::new();
|
|
681
|
+
let mut explicit = false;
|
|
676
682
|
if !team_key.is_empty() {
|
|
677
683
|
keys.push(team_key.to_string());
|
|
684
|
+
explicit = true;
|
|
678
685
|
}
|
|
679
686
|
if let Some(active_key) = active_key {
|
|
680
687
|
keys.push(active_key);
|
|
@@ -688,8 +695,41 @@ pub(super) fn sync_restart_team_projections(state: &mut serde_json::Value, team_
|
|
|
688
695
|
keys.sort();
|
|
689
696
|
keys.dedup();
|
|
690
697
|
for key in keys {
|
|
691
|
-
|
|
698
|
+
let is_operation_target = explicit && key == team_key;
|
|
699
|
+
if is_operation_target {
|
|
700
|
+
teams.insert(key, compact.clone());
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
let Some(existing) = teams.get(&key) else {
|
|
704
|
+
// 别名条目不存在:不新建,避免把 hack alias 变成真队。
|
|
705
|
+
continue;
|
|
706
|
+
};
|
|
707
|
+
if json_team_identity_matches(existing, &compact) {
|
|
708
|
+
teams.insert(key, compact.clone());
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/// 0.5.28: 别名同步身份门。比较 existing 条目与 compact(即将写入)四个身份字段:
|
|
714
|
+
/// `team_key`/`session_name`/`team_dir`/`spec_path`。legacy 缺字段容忍(两侧任一
|
|
715
|
+
/// 缺则该字段不参与判定);已有字段必须相等,冲突即拒绝覆盖。
|
|
716
|
+
fn json_team_identity_matches(existing: &serde_json::Value, compact: &serde_json::Value) -> bool {
|
|
717
|
+
const FIELDS: &[&str] = &["team_key", "session_name", "team_dir", "spec_path"];
|
|
718
|
+
for field in FIELDS {
|
|
719
|
+
let lhs = existing
|
|
720
|
+
.get(field)
|
|
721
|
+
.and_then(serde_json::Value::as_str)
|
|
722
|
+
.filter(|s| !s.is_empty());
|
|
723
|
+
let rhs = compact
|
|
724
|
+
.get(field)
|
|
725
|
+
.and_then(serde_json::Value::as_str)
|
|
726
|
+
.filter(|s| !s.is_empty());
|
|
727
|
+
match (lhs, rhs) {
|
|
728
|
+
(Some(a), Some(b)) if a != b => return false,
|
|
729
|
+
_ => {}
|
|
730
|
+
}
|
|
692
731
|
}
|
|
732
|
+
true
|
|
693
733
|
}
|
|
694
734
|
|
|
695
735
|
pub(super) fn state_session_name(state: &serde_json::Value) -> SessionName {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.28",
|
|
4
4
|
"description": "npx installer for Team Agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codex",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"team-agent-installer": "npm/install.mjs"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@team-agent/cli-darwin-arm64": "0.5.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.5.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.5.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.5.28",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.28",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.28"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|