@team-agent/installer 0.5.103 → 0.5.105

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 (40) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +2 -0
  4. package/crates/team-agent/src/cli/emit.rs +102 -5
  5. package/crates/team-agent/src/cli/mod.rs +8 -14
  6. package/crates/team-agent/src/cli/spec.rs +3 -3
  7. package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -0
  8. package/crates/team-agent/src/cli/types.rs +3 -1
  9. package/crates/team-agent/src/compiler/tests.rs +19 -27
  10. package/crates/team-agent/src/compiler.rs +11 -105
  11. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +6 -2
  12. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +10 -6
  13. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +2 -5
  14. package/crates/team-agent/src/lifecycle/launch/identity.rs +9 -13
  15. package/crates/team-agent/src/lifecycle/launch/role_source.rs +2 -26
  16. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -0
  17. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +7 -2
  18. package/crates/team-agent/src/lifecycle/launch.rs +4 -1
  19. package/crates/team-agent/src/lifecycle/mod.rs +1 -0
  20. package/crates/team-agent/src/lifecycle/profile_launch.rs +0 -12
  21. package/crates/team-agent/src/lifecycle/restart/agent.rs +31 -0
  22. package/crates/team-agent/src/lifecycle/restart/avatar.rs +0 -1
  23. package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
  24. package/crates/team-agent/src/lifecycle/restart.rs +1 -1
  25. package/crates/team-agent/src/lifecycle/role_config.rs +1123 -0
  26. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +7 -19
  27. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +8 -41
  28. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +14 -8
  29. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +8 -4
  30. package/crates/team-agent/src/lifecycle/worker_command_context.rs +4 -4
  31. package/crates/team-agent/src/model/enums.rs +0 -9
  32. package/crates/team-agent/src/model/spec.rs +2 -2
  33. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +7 -8
  34. package/crates/team-agent/src/provider/adapters/grok.rs +5 -18
  35. package/crates/team-agent/src/provider/tests/adapter.rs +15 -24
  36. package/crates/team-agent/src/provider/wire.rs +2 -29
  37. package/crates/team-agent/src/state/repository.rs +2 -1
  38. package/package.json +4 -4
  39. package/skills/team-agent/SKILL.md +83 -33
  40. package/skills/team-agent/references/team-agent-operator.md +54 -523
@@ -55,6 +55,7 @@ pub(super) fn rollback_add_agent_atomic(
55
55
  spec_path: &Path,
56
56
  pre_spec_text: Option<&str>,
57
57
  pre_runtime_state: Option<&serde_json::Value>,
58
+ team_key: Option<&str>,
58
59
  agent_id: &AgentId,
59
60
  reason: &str,
60
61
  ) {
@@ -86,7 +87,7 @@ pub(super) fn rollback_add_agent_atomic(
86
87
  crate::state::repository::StateRepository::new(run_workspace)
87
88
  .save(
88
89
  crate::state::repository::StateWriteIntent::AgentRollback {
89
- team_key: None,
90
+ team_key,
90
91
  agent_id: agent_id.as_str(),
91
92
  },
92
93
  state,
@@ -94,7 +95,7 @@ pub(super) fn rollback_add_agent_atomic(
94
95
  .is_ok()
95
96
  } else {
96
97
  // No prior runtime state — drop just the agent we added (load → strip → save).
97
- if let Ok(mut state) = crate::state::persist::load_runtime_state(run_workspace) {
98
+ if let Ok(mut state) = crate::state::projection::select_runtime_state(run_workspace, team_key) {
98
99
  if let Some(agents) = state
99
100
  .get_mut("agents")
100
101
  .and_then(serde_json::Value::as_object_mut)
@@ -105,7 +106,8 @@ pub(super) fn rollback_add_agent_atomic(
105
106
  .get_mut("teams")
106
107
  .and_then(serde_json::Value::as_object_mut)
107
108
  {
108
- for team in teams.values_mut() {
109
+ for (key, team) in teams.iter_mut() {
110
+ if team_key.is_some_and(|selected| selected != key) { continue; }
109
111
  if let Some(agents) = team
110
112
  .get_mut("agents")
111
113
  .and_then(serde_json::Value::as_object_mut)
@@ -117,7 +119,7 @@ pub(super) fn rollback_add_agent_atomic(
117
119
  crate::state::repository::StateRepository::new(run_workspace)
118
120
  .save(
119
121
  crate::state::repository::StateWriteIntent::AgentRollback {
120
- team_key: None,
122
+ team_key,
121
123
  agent_id: agent_id.as_str(),
122
124
  },
123
125
  &state,
@@ -390,7 +392,8 @@ fn starting_agent_entry(
390
392
  let provider = meta
391
393
  .get("provider")
392
394
  .and_then(Value::as_str)
393
- .unwrap_or("codex");
395
+ .filter(|provider| !provider.trim().is_empty())
396
+ .unwrap_or("pi");
394
397
  let auth_mode = meta
395
398
  .get("auth_mode")
396
399
  .and_then(Value::as_str)
@@ -438,8 +441,9 @@ fn starting_agent_entry(
438
441
  let meta_provider = meta
439
442
  .get("provider")
440
443
  .and_then(Value::as_str)
444
+ .filter(|provider| !provider.trim().is_empty())
441
445
  .and_then(crate::provider::wire::parse_provider)
442
- .unwrap_or(Provider::Codex);
446
+ .unwrap_or(Provider::Pi);
443
447
  persist_effective_approval_policy_from_yaml_agent(obj, meta, meta_provider);
444
448
  }
445
449
  entry
@@ -59,12 +59,9 @@ pub fn clone_agent(
59
59
  source_agent_id,
60
60
  as_agent_id,
61
61
  label,
62
- None,
63
62
  )?;
64
- // The materialized role carries the source role's DECLARED tools verbatim —
65
- // a clone must preserve the source seat's full tools set (add-agent does
66
- // the same; no leader-ceiling clamp). See role_source.rs for the removed
67
- // clamp_materialized_role_to_leader.
63
+ // Materialization preserves the role body and legacy metadata; the compiler
64
+ // only maps supported fields, so ignored header keys remain behaviorally inert.
68
65
  let added = add_agent(
69
66
  &selected.run_workspace,
70
67
  as_agent_id,
@@ -168,25 +168,21 @@ pub(crate) fn provider_effort_event_payload(
168
168
  }
169
169
 
170
170
  /// ---
171
- /// purpose: 对 claude 系 provider 兜底确保 CLAUDE_EFFORT 进入待清除环境变量表
171
+ /// purpose: 仅在存在显式 effort override 时清除继承的 CLAUDE_EFFORT
172
172
  /// params:
173
173
  /// base: 已有的待清除变量名列表
174
- /// returns: 非 claude 系原样返回;claude 系在缺失时补上 CLAUDE_EFFORT
175
- /// ---
176
- /// 0.4.x provider effort MVP step 9: defensive guarantee that `CLAUDE_EFFORT`
177
- /// is unset in the Claude/ClaudeCode worker spawn env. As of the
178
- /// `profile_launch::provider_env_unsets` update, the base list already
179
- /// includes `CLAUDE_EFFORT` for Claude — so this function is idempotent
180
- /// (returns input unchanged). Kept as a belt-and-braces guard so a future
181
- /// refactor that bypasses provider_env_unsets cannot silently drop the
182
- /// scrub. The structural win is in `tmux_backend::shell_command` which now
183
- /// filters env exports by env_unset (preventing inherited values from
184
- /// re-introducing keys we just unset).
174
+ /// has_effort_override: 本次启动是否实际施加 effort
175
+ /// returns: 无显式 override 或非 claude 系时原样返回
176
+ /// ---
177
+ /// Preserve provider-native `CLAUDE_EFFORT` unless a supported explicit role
178
+ /// effort is applied; in that case prevent the inherited environment from
179
+ /// overriding the requested value.
185
180
  pub(crate) fn extend_worker_env_unset_for_effort(
186
181
  base: Vec<String>,
187
182
  provider: Provider,
183
+ has_effort_override: bool,
188
184
  ) -> Vec<String> {
189
- if !matches!(provider, Provider::Claude | Provider::ClaudeCode) {
185
+ if !has_effort_override || !matches!(provider, Provider::Claude | Provider::ClaudeCode) {
190
186
  return base;
191
187
  }
192
188
  let mut out = base;
@@ -39,7 +39,7 @@ fn set_yaml_map_value(value: &mut Value, key: &str, next: Value) -> Result<(), L
39
39
  Ok(())
40
40
  }
41
41
 
42
- fn dump_role_frontmatter(meta: &Value) -> String {
42
+ pub(crate) fn dump_role_frontmatter(meta: &Value) -> String {
43
43
  let Value::Map(pairs) = meta else {
44
44
  return yaml::dumps(meta);
45
45
  };
@@ -132,9 +132,8 @@ impl Drop for MaterializedRole {
132
132
  /// state: 用于找源席的 dynamic_role_file,找不到时退到 team 目录下的同名 md
133
133
  /// as_agent_id: 新席位名,写进 front matter 的 name
134
134
  /// label: 非空时覆盖 front matter 的 role
135
- /// agent_label: 非空时写入 front matter 的 label
136
135
  /// returns: 物化结果,未调用 keep 时 Drop 会删掉该文件
137
- /// errors: 源文件缺失、未声明 name 或声明与源席不符时返回 Compile;目标已存在返回 RequirementUnmet;建目录或写盘失败返回 StatePersist
136
+ /// errors: 源文件缺失时返回 Compile;目标已存在返回 RequirementUnmet;建目录或写盘失败返回 StatePersist
138
137
  /// ---
139
138
  pub(crate) fn materialize_latest_role(
140
139
  run_workspace: &Path,
@@ -143,27 +142,10 @@ pub(crate) fn materialize_latest_role(
143
142
  source_agent_id: &AgentId,
144
143
  as_agent_id: &AgentId,
145
144
  label: Option<&str>,
146
- agent_label: Option<&str>,
147
145
  ) -> Result<MaterializedRole, LifecycleError> {
148
146
  let source_path = resolve_role_source(run_workspace, team_dir, state, source_agent_id)?;
149
147
  let (mut meta, body) = crate::compiler::read_front_matter(&source_path)
150
148
  .map_err(|error| LifecycleError::Compile(error.to_string()))?;
151
- let declared = meta
152
- .get("name")
153
- .and_then(Value::as_str)
154
- .filter(|name| !name.is_empty())
155
- .ok_or_else(|| {
156
- LifecycleError::Compile(format!(
157
- "source role file does not declare name: {}",
158
- source_path.display()
159
- ))
160
- })?;
161
- if declared != source_agent_id.as_str() {
162
- return Err(LifecycleError::Compile(format!(
163
- "source role file declares name '{}' but source agent is '{}'",
164
- declared, source_agent_id
165
- )));
166
- }
167
149
  set_yaml_map_value(
168
150
  &mut meta,
169
151
  "name",
@@ -175,12 +157,6 @@ pub(crate) fn materialize_latest_role(
175
157
  {
176
158
  set_yaml_map_value(&mut meta, "role", Value::Str(label.to_string()))?;
177
159
  }
178
- if let Some(agent_label) = agent_label
179
- .map(strip_label_quotes)
180
- .filter(|value| !value.is_empty())
181
- {
182
- set_yaml_map_value(&mut meta, "label", Value::Str(agent_label.to_string()))?;
183
- }
184
160
  if let Some(role) = meta.get("role").and_then(Value::as_str).map(str::to_string) {
185
161
  let role_without_quotes = strip_label_quotes(&role);
186
162
  if !role_without_quotes.is_empty() && role_without_quotes != role {
@@ -255,6 +255,7 @@ pub(super) fn spawn_agents(
255
255
  extend_worker_env_unset_for_effort(
256
256
  profile_launch.env_unset.iter().cloned().collect(),
257
257
  provider,
258
+ agent_effort.is_some(),
258
259
  ),
259
260
  );
260
261
  // The provider is the final typed value after profile overlays. Keep it
@@ -327,8 +327,13 @@ pub(crate) fn write_spec_atomic(spec_path: &Path, spec: &Value) -> Result<(), Li
327
327
  .map_err(|e| LifecycleError::StatePersist(format!("{}: {e}", parent.display())))?;
328
328
  }
329
329
  let tmp = spec_path.with_extension(format!("tmp-{}", std::process::id()));
330
- std::fs::write(&tmp, yaml::dumps(spec))
331
- .map_err(|e| LifecycleError::StatePersist(format!("{}: {e}", tmp.display())))?;
330
+ if let Err(e) = std::fs::write(&tmp, yaml::dumps(spec)) {
331
+ let _ = std::fs::remove_file(&tmp);
332
+ return Err(LifecycleError::StatePersist(format!(
333
+ "{}: {e}",
334
+ tmp.display()
335
+ )));
336
+ }
332
337
  if let Err(e) = std::fs::rename(&tmp, spec_path) {
333
338
  let _ = std::fs::remove_file(&tmp);
334
339
  return Err(LifecycleError::StatePersist(format!(
@@ -402,7 +402,10 @@ pub(super) use readiness::*;
402
402
  mod add_agent;
403
403
  pub(super) use add_agent::*;
404
404
  pub use add_agent::{add_agent, add_agent_force};
405
- pub(crate) use add_agent::{add_agent_with_transport, add_agent_with_transport_force};
405
+ pub(crate) use add_agent::{
406
+ add_agent_with_transport, add_agent_with_transport_force,
407
+ add_agent_with_transport_at_paths_locked, force_recreate_with_transport_locked,
408
+ };
406
409
 
407
410
  pub(crate) mod add_agent_state;
408
411
  pub(super) use add_agent_state::*;
@@ -57,6 +57,7 @@ pub(crate) mod pane_input_lock;
57
57
  pub(crate) mod profile_launch;
58
58
  pub(crate) mod profile_smoke;
59
59
  pub mod restart;
60
+ pub mod role_config;
60
61
  pub mod types;
61
62
  pub(crate) mod worker_command_context;
62
63
 
@@ -578,18 +578,6 @@ pub(crate) fn provider_env_unsets(provider: Provider, auth_mode: AuthMode) -> BT
578
578
  unsets.insert("CLAUDE_CODE_ENTRYPOINT".to_string());
579
579
  unsets.insert("CLAUDE_CODE_EXECPATH".to_string());
580
580
  unsets.insert("CLAUDECODE".to_string());
581
- // 0.4.x provider effort MVP step 9: CLAUDE_EFFORT carried from the
582
- // launching shell would silently override the framework's effort
583
- // decision (or, when no spec effort is configured, override the
584
- // documented "provider default"). Scrubbing here is the single
585
- // source — `extend_worker_env_unset_for_effort` is a thin alias
586
- // that calls into this list. Applies to BOTH worker spawn AND
587
- // leader spawn (leader_env_unset_for_provider also reads this).
588
- // MVP scope: leader-effort is not configured yet, but if a
589
- // launching shell already carries CLAUDE_EFFORT, the leader
590
- // inheriting it would conflict with future leader-effort
591
- // semantics. Scrub uniformly for predictability.
592
- unsets.insert("CLAUDE_EFFORT".to_string());
593
581
  if auth_mode == AuthMode::CompatibleApi {
594
582
  unsets.insert("ANTHROPIC_API_KEY".to_string());
595
583
  }
@@ -37,6 +37,37 @@ use super::team_state::write_team_state;
37
37
  use super::*;
38
38
  use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
39
39
 
40
+ /// CLI start is orthogonal to stop: refuse any live target before editing its role.
41
+ /// Transport uncertainty is a refusal, not permission to spawn a duplicate.
42
+ pub(crate) fn ensure_agent_not_running(
43
+ state: &serde_json::Value,
44
+ agent_id: &AgentId,
45
+ transport: &dyn crate::transport::Transport,
46
+ ) -> Result<(), LifecycleError> {
47
+ let agent = state.get("agents").and_then(|agents| agents.get(agent_id.as_str()))
48
+ .ok_or_else(|| LifecycleError::RequirementUnmet(format!("agent {agent_id} not found")))?;
49
+ let session = state_session_name(state);
50
+ let window = agent_window(agent, agent_id);
51
+ let pane = agent.get("pane_id").and_then(serde_json::Value::as_str);
52
+ let targets = transport.list_targets()
53
+ .map_err(|error| LifecycleError::Transport(error.to_string()))?;
54
+ let live = targets.iter().find(|target| {
55
+ target.session == session
56
+ && target.window_name.as_ref().is_some_and(|name| name.as_str() == window)
57
+ && (pane == Some(target.pane_id.as_str())
58
+ || !crate::lifecycle::launch::state_uses_adaptive_layout(state))
59
+ && agent_pane_live_by_id(transport, &target.pane_id)
60
+ });
61
+ if let Some(target) = live {
62
+ Err(LifecycleError::RequirementUnmet(format!(
63
+ "agent {agent_id} is already running (duplicate / active cohort); use stop-agent first; cohort proof: session={} window={window} pane={}",
64
+ session.as_str(), target.pane_id.as_str(),
65
+ )))
66
+ } else {
67
+ Ok(())
68
+ }
69
+ }
70
+
40
71
  /// ---
41
72
  /// purpose: 起或复活一席的对外入口
42
73
  /// params:
@@ -315,7 +315,6 @@ pub(crate) fn fork_pi_new_seat_locked(
315
315
  source_agent_id,
316
316
  target_agent_id,
317
317
  label,
318
- Some(target_agent_id.as_str()),
319
318
  )?;
320
319
  role = Some(target_role);
321
320
  let target_role_path = role
@@ -510,6 +510,7 @@ pub(super) fn spawn_agent_window(
510
510
  crate::lifecycle::launch::extend_worker_env_unset_for_effort(
511
511
  profile_launch.env_unset.iter().cloned().collect(),
512
512
  provider,
513
+ restart_effort.is_some(),
513
514
  ),
514
515
  );
515
516
  // Keep the launcher-known typed provider independent from inherited
@@ -748,7 +749,6 @@ fn merge_command_context_fields(
748
749
  "auth_mode",
749
750
  "effort",
750
751
  "profile",
751
- "permission_mode",
752
752
  // 0.5.66 bypass 单源:rehydrate 同步合入,restart 的 safety 构造读到新值。
753
753
  "dangerously_skip_permissions",
754
754
  ] {
@@ -77,7 +77,7 @@ pub(crate) mod remove;
77
77
  mod selection;
78
78
  mod team_state;
79
79
 
80
- pub(crate) use agent::start_agent_at_paths;
80
+ pub(crate) use agent::{start_agent_at_paths, ensure_agent_not_running};
81
81
  pub use agent::{reset_agent, start_agent, stop_agent};
82
82
  pub(crate) use agent::{
83
83
  reset_agent_with_transport, start_agent_with_transport, stop_agent_with_transport,