@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +2 -0
- package/crates/team-agent/src/cli/emit.rs +102 -5
- package/crates/team-agent/src/cli/mod.rs +8 -14
- package/crates/team-agent/src/cli/spec.rs +3 -3
- package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -0
- package/crates/team-agent/src/cli/types.rs +3 -1
- package/crates/team-agent/src/compiler/tests.rs +19 -27
- package/crates/team-agent/src/compiler.rs +11 -105
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +6 -2
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +10 -6
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +2 -5
- package/crates/team-agent/src/lifecycle/launch/identity.rs +9 -13
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +2 -26
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +7 -2
- package/crates/team-agent/src/lifecycle/launch.rs +4 -1
- package/crates/team-agent/src/lifecycle/mod.rs +1 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +0 -12
- package/crates/team-agent/src/lifecycle/restart/agent.rs +31 -0
- package/crates/team-agent/src/lifecycle/restart/avatar.rs +0 -1
- package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart.rs +1 -1
- package/crates/team-agent/src/lifecycle/role_config.rs +1123 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +7 -19
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +8 -41
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +14 -8
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +8 -4
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +4 -4
- package/crates/team-agent/src/model/enums.rs +0 -9
- package/crates/team-agent/src/model/spec.rs +2 -2
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +7 -8
- package/crates/team-agent/src/provider/adapters/grok.rs +5 -18
- package/crates/team-agent/src/provider/tests/adapter.rs +15 -24
- package/crates/team-agent/src/provider/wire.rs +2 -29
- package/crates/team-agent/src/state/repository.rs +2 -1
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +83 -33
- 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
|
|
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::
|
|
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.
|
|
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
|
|
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
|
-
.
|
|
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::
|
|
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
|
-
//
|
|
65
|
-
//
|
|
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:
|
|
171
|
+
/// purpose: 仅在存在显式 effort override 时清除继承的 CLAUDE_EFFORT
|
|
172
172
|
/// params:
|
|
173
173
|
/// base: 已有的待清除变量名列表
|
|
174
|
-
///
|
|
175
|
-
///
|
|
176
|
-
///
|
|
177
|
-
///
|
|
178
|
-
///
|
|
179
|
-
///
|
|
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:
|
|
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
|
-
|
|
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::{
|
|
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::*;
|
|
@@ -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:
|
|
@@ -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,
|