@team-agent/installer 0.4.1 → 0.4.3

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.
Files changed (44) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +82 -22
  4. package/crates/team-agent/src/cli/diagnose.rs +6 -2
  5. package/crates/team-agent/src/cli/emit.rs +301 -38
  6. package/crates/team-agent/src/cli/mod.rs +7 -14
  7. package/crates/team-agent/src/cli/status_port.rs +12 -1
  8. package/crates/team-agent/src/cli/tests/base.rs +6 -2
  9. package/crates/team-agent/src/cli/tests/lane_c.rs +1 -1
  10. package/crates/team-agent/src/cli/tests/leader_watch.rs +5 -3
  11. package/crates/team-agent/src/cli/tests/main_preserved.rs +28 -2
  12. package/crates/team-agent/src/cli/tests/peer_allow.rs +19 -0
  13. package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +72 -0
  14. package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -3
  15. package/crates/team-agent/src/cli/tests/status_send.rs +1 -0
  16. package/crates/team-agent/src/cli/types.rs +35 -2
  17. package/crates/team-agent/src/coordinator/runtime_detectors.rs +7 -2
  18. package/crates/team-agent/src/diagnose/comms.rs +10 -2
  19. package/crates/team-agent/src/leader/lease.rs +98 -19
  20. package/crates/team-agent/src/leader/rediscover/tests.rs +21 -7
  21. package/crates/team-agent/src/leader/rediscover.rs +16 -7
  22. package/crates/team-agent/src/leader/start.rs +25 -12
  23. package/crates/team-agent/src/leader/tests/byte_findings.rs +11 -2
  24. package/crates/team-agent/src/leader/tests/lease_claim.rs +248 -7
  25. package/crates/team-agent/src/lifecycle/launch.rs +126 -100
  26. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +40 -12
  27. package/crates/team-agent/src/lifecycle/tests/core.rs +1 -1
  28. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +16 -15
  29. package/crates/team-agent/src/lifecycle/types.rs +1 -1
  30. package/crates/team-agent/src/messaging/leader_receiver.rs +22 -8
  31. package/crates/team-agent/src/messaging/send.rs +21 -4
  32. package/crates/team-agent/src/provider/adapter.rs +72 -0
  33. package/crates/team-agent/src/provider/session/capture.rs +228 -0
  34. package/crates/team-agent/src/state/identity.rs +42 -12
  35. package/crates/team-agent/src/state/identity_keys.rs +134 -0
  36. package/crates/team-agent/src/state/mod.rs +8 -0
  37. package/crates/team-agent/src/state/owner_gate.rs +11 -1
  38. package/crates/team-agent/src/state/ownership.rs +556 -0
  39. package/crates/team-agent/src/state/paths.rs +358 -0
  40. package/crates/team-agent/src/state/persist.rs +43 -5
  41. package/crates/team-agent/src/state/projection.rs +13 -5
  42. package/crates/team-agent/src/tmux_backend.rs +12 -0
  43. package/package.json +4 -4
  44. package/skills/team-agent/SKILL.md +3 -3
@@ -0,0 +1,134 @@
1
+ //! Stage 0 of the identity-boundary unified plan (architect direction
2
+ //! 2026-06-23): `SessionAttributionKey`.
3
+ //!
4
+ //! Pure additive: nobody constructs this type yet. Stage 1 (Claude strict
5
+ //! capture) starts populating it inside `CaptureSessionContext`; Stage 4
6
+ //! (CLI/DB scope gate) starts using it as the DB/event filter key.
7
+ //!
8
+ //! Why a struct, not a tuple:
9
+ //! - The four fields are not interchangeable: `provider` selects the
10
+ //! tombstone/claimed-session table, `team_key` is the multi-team boundary,
11
+ //! `agent_id` is the worker identity inside a team, `session_id` is the
12
+ //! provider-issued transcript identity.
13
+ //! - The architect's verdict §1 names this exact shape as the "shared
14
+ //! identity primitive" that owner / capture / multi-team all need so they
15
+ //! don't drift into per-module ad-hoc keys.
16
+ //!
17
+ //! Single-team behaviour: `team_key` is still populated (from
18
+ //! `state::projection::team_state_key` or the same selector path Stage 0
19
+ //! introduces). The key is invariant — capture/owner/CLI all read it from
20
+ //! the same source.
21
+
22
+ use crate::model::enums::Provider;
23
+
24
+ /// The canonical identity of a "this worker owns this provider transcript"
25
+ /// claim. Stage 1 + Stage 4 will both store and compare via this struct.
26
+ ///
27
+ /// All four fields are non-empty by construction (the constructor refuses
28
+ /// empty `team_key`, `agent_id`, or `session_id` — those would collapse
29
+ /// distinct sessions into one).
30
+ #[derive(Debug, Clone, PartialEq, Eq, Hash)]
31
+ pub struct SessionAttributionKey {
32
+ provider: Provider,
33
+ team_key: String,
34
+ agent_id: String,
35
+ session_id: String,
36
+ }
37
+
38
+ impl SessionAttributionKey {
39
+ pub fn new(
40
+ provider: Provider,
41
+ team_key: impl Into<String>,
42
+ agent_id: impl Into<String>,
43
+ session_id: impl Into<String>,
44
+ ) -> Option<Self> {
45
+ let team_key = team_key.into();
46
+ let agent_id = agent_id.into();
47
+ let session_id = session_id.into();
48
+ if team_key.is_empty() || agent_id.is_empty() || session_id.is_empty() {
49
+ return None;
50
+ }
51
+ Some(Self {
52
+ provider,
53
+ team_key,
54
+ agent_id,
55
+ session_id,
56
+ })
57
+ }
58
+
59
+ pub fn provider(&self) -> Provider {
60
+ self.provider
61
+ }
62
+
63
+ pub fn team_key(&self) -> &str {
64
+ &self.team_key
65
+ }
66
+
67
+ pub fn agent_id(&self) -> &str {
68
+ &self.agent_id
69
+ }
70
+
71
+ pub fn session_id(&self) -> &str {
72
+ &self.session_id
73
+ }
74
+ }
75
+
76
+ #[cfg(test)]
77
+ mod tests {
78
+ use super::*;
79
+
80
+ #[test]
81
+ fn refuses_empty_team_key() {
82
+ assert!(
83
+ SessionAttributionKey::new(Provider::Claude, "", "agent_a", "sess_1").is_none()
84
+ );
85
+ }
86
+
87
+ #[test]
88
+ fn refuses_empty_agent_id() {
89
+ assert!(
90
+ SessionAttributionKey::new(Provider::Claude, "alpha", "", "sess_1").is_none()
91
+ );
92
+ }
93
+
94
+ #[test]
95
+ fn refuses_empty_session_id() {
96
+ assert!(
97
+ SessionAttributionKey::new(Provider::Claude, "alpha", "agent_a", "").is_none()
98
+ );
99
+ }
100
+
101
+ #[test]
102
+ fn accepts_well_formed_key_and_preserves_fields() {
103
+ let key = SessionAttributionKey::new(
104
+ Provider::Claude,
105
+ "alpha",
106
+ "reviewer",
107
+ "36dd1d1a-2766-4636-856c-03f14a4bb803",
108
+ )
109
+ .unwrap();
110
+ assert_eq!(key.provider(), Provider::Claude);
111
+ assert_eq!(key.team_key(), "alpha");
112
+ assert_eq!(key.agent_id(), "reviewer");
113
+ assert_eq!(key.session_id(), "36dd1d1a-2766-4636-856c-03f14a4bb803");
114
+ }
115
+
116
+ /// Same provider + agent_id + session_id but different team_key → distinct
117
+ /// identity. This is the multi-team isolation invariant the unified plan
118
+ /// requires (architect §1 cross point: tombstone/claimed by full
119
+ /// (provider, team_key, agent_id, session_id) tuple).
120
+ #[test]
121
+ fn different_team_keys_produce_different_keys() {
122
+ let alpha = SessionAttributionKey::new(Provider::Claude, "alpha", "reviewer", "s1").unwrap();
123
+ let beta = SessionAttributionKey::new(Provider::Claude, "beta", "reviewer", "s1").unwrap();
124
+ assert_ne!(alpha, beta);
125
+ }
126
+
127
+ /// Same team but different agent_id → distinct (per-worker isolation).
128
+ #[test]
129
+ fn different_agent_ids_produce_different_keys() {
130
+ let reviewer = SessionAttributionKey::new(Provider::Claude, "alpha", "reviewer", "s1").unwrap();
131
+ let frontend = SessionAttributionKey::new(Provider::Claude, "alpha", "frontend", "s1").unwrap();
132
+ assert_ne!(reviewer, frontend);
133
+ }
134
+ }
@@ -15,7 +15,15 @@
15
15
  #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
16
16
 
17
17
  pub mod identity;
18
+ // Stage 0 of identity-boundary unified plan (architect direction 2026-06-23):
19
+ // shared identity primitives. Pure additive — Stage 1+ consume these.
20
+ pub mod identity_keys;
18
21
  pub mod owner_gate;
22
+ // Stage 2 of identity-boundary unified plan: single read entry point for
23
+ // `team_owner` lookups. Stage 3 will migrate writers; Stage 5 will swap
24
+ // the data source to per-team canonical state without touching call sites.
25
+ pub mod ownership;
26
+ pub mod paths;
19
27
  pub mod persist;
20
28
  pub mod projection;
21
29
  pub mod selector;
@@ -39,13 +39,23 @@ pub struct CallerIdentity {
39
39
 
40
40
  /// `check_team_owner`(`state.py:366`)的纯判定版:caller 身份 + 是否有 TEAM_AGENT_ID + liveness 注入。
41
41
  /// 返回 `None`=允许(own / 无 owner / 死 owner pane 可接管);`Some(dict)`=拒绝(team_owner_mismatch)。
42
+ ///
43
+ /// Stage 2 (identity-boundary unified plan, architect direction 2026-06-23):
44
+ /// the owner is now looked up through `state::ownership::read_owner_value`
45
+ /// instead of `state::projection::read_owner` directly. The two are
46
+ /// equivalent today for the gate's `None` (empty team_key) path — both
47
+ /// resolve to top-level `state.team_owner` — but routing through the
48
+ /// repository means Stage 5 can swap the data source (per-team canonical
49
+ /// `state.json`) without touching this gate. The empty-team-key argument
50
+ /// preserves the pre-Stage-2 shape: gate callers feed an
51
+ /// already-team-projected state and expect top-level reads.
42
52
  pub fn check_team_owner(
43
53
  state: &Value,
44
54
  caller: &CallerIdentity,
45
55
  has_team_agent_id: bool,
46
56
  liveness: &dyn PaneLivenessProbe,
47
57
  ) -> Option<Value> {
48
- let owner = crate::state::projection::read_owner(state, None)?;
58
+ let owner = crate::state::ownership::read_owner_value(state, "")?;
49
59
  if !owner.is_object() || owner.as_object().is_none_or(serde_json::Map::is_empty) {
50
60
  return None;
51
61
  }