@team-agent/installer 0.5.112 → 0.5.114
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 +252 -84
- package/Cargo.toml +3 -1
- package/crates/team-agent/Cargo.toml +3 -0
- package/crates/team-agent/src/cli/adapters.rs +119 -50
- package/crates/team-agent/src/cli/diagnose.rs +36 -13
- package/crates/team-agent/src/cli/emit.rs +173 -109
- package/crates/team-agent/src/cli/leader.rs +16 -0
- package/crates/team-agent/src/cli/leader_prompt.rs +261 -0
- package/crates/team-agent/src/cli/leaders.rs +32 -32
- package/crates/team-agent/src/cli/mod.rs +158 -14
- package/crates/team-agent/src/cli/models.rs +68 -18
- package/crates/team-agent/src/cli/named_address.rs +17 -16
- package/crates/team-agent/src/cli/route.rs +4 -4
- package/crates/team-agent/src/cli/send/presentation.rs +19 -18
- package/crates/team-agent/src/cli/send/resolve.rs +11 -11
- package/crates/team-agent/src/cli/send.rs +4 -4
- package/crates/team-agent/src/cli/spec.rs +35 -29
- package/crates/team-agent/src/cli/status.rs +7 -7
- package/crates/team-agent/src/cli/status_port/brief.rs +268 -101
- package/crates/team-agent/src/cli/tests/base.rs +94 -6
- package/crates/team-agent/src/cli/tests/divergence.rs +3 -2
- package/crates/team-agent/src/cli/tests/lane_c.rs +1 -1
- package/crates/team-agent/src/cli/tests/named_address.rs +3 -3
- package/crates/team-agent/src/cli/tests/status_send.rs +9 -7
- package/crates/team-agent/src/cli/triage.rs +53 -2
- package/crates/team-agent/src/cli/types.rs +8 -8
- package/crates/team-agent/src/compiler/tests.rs +12 -0
- package/crates/team-agent/src/compiler.rs +65 -26
- package/crates/team-agent/src/contract_runtime/backend.rs +646 -0
- package/crates/team-agent/src/contract_runtime/config.rs +309 -0
- package/crates/team-agent/src/contract_runtime/discovery.rs +237 -0
- package/crates/team-agent/src/contract_runtime/forward.rs +229 -0
- package/crates/team-agent/src/contract_runtime/framework.rs +261 -0
- package/crates/team-agent/src/contract_runtime/mcp.rs +396 -0
- package/crates/team-agent/src/contract_runtime/mod.rs +14 -0
- package/crates/team-agent/src/contract_runtime/prompt.rs +216 -0
- package/crates/team-agent/src/contract_runtime/pump.rs +207 -0
- package/crates/team-agent/src/contract_runtime/registry.rs +30 -0
- package/crates/team-agent/src/coordinator/tick.rs +26 -8
- package/crates/team-agent/src/leader/mod.rs +1 -0
- package/crates/team-agent/src/leader/prompt.rs +906 -0
- package/crates/team-agent/src/leader/start.rs +265 -16
- package/crates/team-agent/src/leader/tests/identity.rs +21 -0
- package/crates/team-agent/src/leader/tests/mod.rs +2 -0
- package/crates/team-agent/src/leader/tests/prompt_contract.rs +268 -0
- package/crates/team-agent/src/leader/types.rs +6 -0
- package/crates/team-agent/src/lib.rs +4 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +172 -13
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +760 -365
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +38 -9
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +17 -0
- package/crates/team-agent/src/lifecycle/launch.rs +39 -28
- package/crates/team-agent/src/lifecycle/profile_launch.rs +58 -46
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +97 -52
- package/crates/team-agent/src/lifecycle/restart.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +3 -2
- package/crates/team-agent/src/lifecycle/types.rs +4 -1
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +37 -27
- package/crates/team-agent/src/main.rs +6 -0
- package/crates/team-agent/src/messaging/delivery.rs +50 -1
- package/crates/team-agent/src/messaging/send.rs +10 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +131 -0
- package/crates/team-agent/src/model/spec.rs +24 -12
- package/crates/team-agent/src/provider/adapter.rs +8 -59
- package/crates/team-agent/src/provider/adapters/claude.rs +6 -14
- package/crates/team-agent/src/provider/adapters/codex.rs +8 -18
- package/crates/team-agent/src/provider/adapters/copilot.rs +5 -22
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +8 -45
- package/crates/team-agent/src/provider/adapters/grok.rs +7 -31
- package/crates/team-agent/src/provider/adapters/pi.rs +7 -18
- package/crates/team-agent/src/provider/command_helpers.rs +45 -0
- package/crates/team-agent/src/provider/mod.rs +1 -0
- package/crates/team-agent/src/provider/model_catalog.rs +21 -28
- package/crates/team-agent/src/provider/tests/adapter.rs +70 -0
- package/crates/team-agent/src/provider/tests/command_helpers.rs +55 -0
- package/crates/team-agent/src/state/persist.rs +73 -0
- package/package.json +4 -4
|
@@ -45,7 +45,7 @@ use super::*;
|
|
|
45
45
|
/// params:
|
|
46
46
|
/// spec_path: spec 路径,team 目录优先取 state 里的 team_dir
|
|
47
47
|
/// session_name: 目标 tmux session
|
|
48
|
-
/// returns:
|
|
48
|
+
/// returns: legacy 启动收据与独立 contract 已提交席位 ID;不为私有 socket 伪造 legacy pane
|
|
49
49
|
/// errors: 命令拼装或 transport spawn 失败时返回 LifecycleError
|
|
50
50
|
/// ---
|
|
51
51
|
pub(super) fn spawn_agents(
|
|
@@ -54,7 +54,7 @@ pub(super) fn spawn_agents(
|
|
|
54
54
|
spec: &Value,
|
|
55
55
|
session_name: &SessionName,
|
|
56
56
|
transport: &dyn Transport,
|
|
57
|
-
) -> Result<Vec<StartedAgent>, LifecycleError> {
|
|
57
|
+
) -> Result<(Vec<StartedAgent>, Vec<AgentId>), LifecycleError> {
|
|
58
58
|
// E5 解耦:team_dir(角色定义 + profiles 所在)≠ spec_path.parent()(spec 已迁出到 .team/runtime)。
|
|
59
59
|
// 优先取 state.team_dir(角色目录),回落 spec_path.parent()(legacy 同目录布局)。
|
|
60
60
|
let team_dir_buf = crate::state::persist::load_runtime_state(workspace)
|
|
@@ -74,6 +74,10 @@ pub(super) fn spawn_agents(
|
|
|
74
74
|
Some(Value::Bool(true))
|
|
75
75
|
);
|
|
76
76
|
let mut started = Vec::new();
|
|
77
|
+
#[cfg(unix)]
|
|
78
|
+
let mut contract_started = Vec::new();
|
|
79
|
+
#[cfg(not(unix))]
|
|
80
|
+
let contract_started = Vec::new();
|
|
77
81
|
for agent in spec_agent_values(spec) {
|
|
78
82
|
let Some(agent_id_raw) = agent.get("id").and_then(Value::as_str) else {
|
|
79
83
|
continue;
|
|
@@ -81,6 +85,26 @@ pub(super) fn spawn_agents(
|
|
|
81
85
|
if agent_is_paused(agent) {
|
|
82
86
|
continue;
|
|
83
87
|
}
|
|
88
|
+
#[cfg(unix)]
|
|
89
|
+
if agent
|
|
90
|
+
.get("provider")
|
|
91
|
+
.and_then(Value::as_str)
|
|
92
|
+
.is_some_and(|provider| {
|
|
93
|
+
crate::contract_runtime::registry::descriptor(provider).is_some()
|
|
94
|
+
})
|
|
95
|
+
{
|
|
96
|
+
let team = runtime_team_key_for_spec(spec_path, spec, session_name);
|
|
97
|
+
crate::contract_runtime::framework::start_role(
|
|
98
|
+
workspace,
|
|
99
|
+
team_dir,
|
|
100
|
+
&team,
|
|
101
|
+
agent_id_raw,
|
|
102
|
+
)?;
|
|
103
|
+
contract_started.push(AgentId::new(agent_id_raw));
|
|
104
|
+
// No legacy target is manufactured for the private native socket.
|
|
105
|
+
// persist_spawn_agent_state projects the actual K3 seat separately.
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
84
108
|
let agent_id = AgentId::new(agent_id_raw);
|
|
85
109
|
let provider = agent
|
|
86
110
|
.get("provider")
|
|
@@ -99,11 +123,12 @@ pub(super) fn spawn_agents(
|
|
|
99
123
|
// has both the role instruction AND the callable Team Agent MCP capability.
|
|
100
124
|
// probe5 RED proved that `build_command(.., None, None, ..)` left the worker
|
|
101
125
|
// without `report_result`; placeholders are substituted at spawn time.
|
|
102
|
-
let command_agent =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
126
|
+
let command_agent =
|
|
127
|
+
crate::lifecycle::worker_command_context::WorkerCommandAgent::from_yaml(
|
|
128
|
+
agent,
|
|
129
|
+
Some(agent_id_raw),
|
|
130
|
+
provider,
|
|
131
|
+
)?;
|
|
107
132
|
let system_prompt =
|
|
108
133
|
crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
|
|
109
134
|
// 0.5.66 bypass 单源:per-agent safety 只服务 env 注入与审计,不参与 argv 决策。
|
|
@@ -431,7 +456,7 @@ pub(super) fn spawn_agents(
|
|
|
431
456
|
managed_mcp_config: plan.managed_mcp_config || profile_launch.managed_mcp_config,
|
|
432
457
|
});
|
|
433
458
|
}
|
|
434
|
-
Ok(started)
|
|
459
|
+
Ok((started, contract_started))
|
|
435
460
|
}
|
|
436
461
|
|
|
437
462
|
/// ---
|
|
@@ -451,7 +476,11 @@ pub(crate) fn agent_id_spec_source_path(workspace: &Path, agent_id: &str) -> Str
|
|
|
451
476
|
});
|
|
452
477
|
team_dir
|
|
453
478
|
.map(|dir| dir.join("agents").join(format!("{agent_id}.md")))
|
|
454
|
-
.unwrap_or_else(||
|
|
479
|
+
.unwrap_or_else(|| {
|
|
480
|
+
workspace
|
|
481
|
+
.join(".team/current/agents")
|
|
482
|
+
.join(format!("{agent_id}.md"))
|
|
483
|
+
})
|
|
455
484
|
.display()
|
|
456
485
|
.to_string()
|
|
457
486
|
}
|
|
@@ -119,6 +119,23 @@ pub(super) fn persist_spawn_agent_state(
|
|
|
119
119
|
let Some(id) = agent.get("id").and_then(Value::as_str) else {
|
|
120
120
|
continue;
|
|
121
121
|
};
|
|
122
|
+
#[cfg(unix)]
|
|
123
|
+
if agent
|
|
124
|
+
.get("provider")
|
|
125
|
+
.and_then(Value::as_str)
|
|
126
|
+
.is_some_and(|provider| {
|
|
127
|
+
crate::contract_runtime::registry::descriptor(provider).is_some()
|
|
128
|
+
})
|
|
129
|
+
{
|
|
130
|
+
let projected = if agent_is_paused(agent) {
|
|
131
|
+
serde_json::json!({"agent_id":id,"provider":"kiro","runtime_family":"contract","status":"paused","pane_id":null})
|
|
132
|
+
} else {
|
|
133
|
+
crate::contract_runtime::framework::project_seat(workspace, &team_id, id)
|
|
134
|
+
.map_err(|error| LifecycleError::StatePersist(error.to_string()))?
|
|
135
|
+
};
|
|
136
|
+
agents.insert(id.to_string(), projected);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
122
139
|
let provider = agent
|
|
123
140
|
.get("provider")
|
|
124
141
|
.and_then(Value::as_str)
|
|
@@ -133,7 +133,11 @@ pub fn launch_with_transport_in_workspace(
|
|
|
133
133
|
// 所有角色都未声明 dangerously_skip_permissions=true → 写 warning 事件不阻塞。
|
|
134
134
|
// TODO(0.6.0): remove(0.6.0 彻底删源头 A 后此警示无意义)。
|
|
135
135
|
emit_dangerous_flag_no_role_declared_warning(workspace, &spec);
|
|
136
|
-
phase_timer.emit(
|
|
136
|
+
phase_timer.emit(
|
|
137
|
+
workspace,
|
|
138
|
+
"launch.phase",
|
|
139
|
+
super::restart::LifecyclePhase::CompileSpec,
|
|
140
|
+
);
|
|
137
141
|
let session_name = spec_session_name(&spec);
|
|
138
142
|
if !dry_run && transport_has_session(transport, &session_name) {
|
|
139
143
|
return Err(LifecycleError::SessionConflict(format!(
|
|
@@ -142,11 +146,16 @@ pub fn launch_with_transport_in_workspace(
|
|
|
142
146
|
)));
|
|
143
147
|
}
|
|
144
148
|
let routes = spec_routes(&spec);
|
|
145
|
-
let started = if dry_run {
|
|
146
|
-
Vec::new()
|
|
149
|
+
let (started, contract_started) = if dry_run {
|
|
150
|
+
(Vec::new(), Vec::new())
|
|
147
151
|
} else {
|
|
148
|
-
phase_timer.emit(
|
|
149
|
-
|
|
152
|
+
phase_timer.emit(
|
|
153
|
+
workspace,
|
|
154
|
+
"launch.phase",
|
|
155
|
+
super::restart::LifecyclePhase::SpawnAll,
|
|
156
|
+
);
|
|
157
|
+
let (started, contract_started) =
|
|
158
|
+
spawn_agents(workspace, spec_path, &spec, &session_name, transport)?;
|
|
150
159
|
persist_spawn_agent_state(
|
|
151
160
|
workspace,
|
|
152
161
|
spec_path,
|
|
@@ -164,14 +173,16 @@ pub fn launch_with_transport_in_workspace(
|
|
|
164
173
|
.first()
|
|
165
174
|
.map(|agent| agent.spawned_at.as_str())
|
|
166
175
|
.unwrap_or(session_name.as_str());
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
if !started.is_empty() {
|
|
177
|
+
transport
|
|
178
|
+
.set_session_owner_with_generation(
|
|
179
|
+
&session_name,
|
|
180
|
+
workspace,
|
|
181
|
+
&owner_team,
|
|
182
|
+
generation,
|
|
183
|
+
)
|
|
184
|
+
.map_err(|error| LifecycleError::Transport(error.to_string()))?;
|
|
185
|
+
}
|
|
175
186
|
// 0.5.38: per-worker timing tags (source="launch") so operators can
|
|
176
187
|
// trace which worker's spawn dominates wall time. Zeros for now on
|
|
177
188
|
// the sub-timings — Step 1 first enables shape assertion; a later
|
|
@@ -208,7 +219,7 @@ pub fn launch_with_transport_in_workspace(
|
|
|
208
219
|
"launch",
|
|
209
220
|
);
|
|
210
221
|
}
|
|
211
|
-
started
|
|
222
|
+
(started, contract_started)
|
|
212
223
|
};
|
|
213
224
|
// 0.3.28 Step 1: topology invariant guard (warn-only during migration).
|
|
214
225
|
// Logs each violation to stderr; never panics. Promoted to hard error at
|
|
@@ -223,6 +234,7 @@ pub fn launch_with_transport_in_workspace(
|
|
|
223
234
|
Ok(LaunchReport {
|
|
224
235
|
session_name,
|
|
225
236
|
started,
|
|
237
|
+
contract_started,
|
|
226
238
|
dry_run,
|
|
227
239
|
tmux_endpoint: transport.tmux_endpoint(),
|
|
228
240
|
routes,
|
|
@@ -348,13 +360,13 @@ pub use cursor_mcp_iso::{
|
|
|
348
360
|
materialize_cursor_mcp_project,
|
|
349
361
|
};
|
|
350
362
|
mod cursor_mcp;
|
|
363
|
+
pub(crate) use cursor_mcp::apply_cursor_mcp_enable_profile_env;
|
|
351
364
|
pub use cursor_mcp::{
|
|
352
365
|
apply_cursor_mcp_overlay, apply_cursor_spawn_workspace_pointers,
|
|
353
366
|
apply_cursor_workspace_physical_path, cursor_mcp_enable_argv, cursor_mcp_enable_working_dir,
|
|
354
367
|
enable_cursor_workspace_mcp, enable_cursor_workspace_mcp_with_profile, physical_workspace_path,
|
|
355
368
|
prepare_cursor_seat_mcp, refuse_second_cursor_occupant,
|
|
356
369
|
};
|
|
357
|
-
pub(crate) use cursor_mcp::apply_cursor_mcp_enable_profile_env;
|
|
358
370
|
|
|
359
371
|
mod cursor_create_chat;
|
|
360
372
|
|
|
@@ -363,9 +375,9 @@ pub(super) use worker_env::*;
|
|
|
363
375
|
pub(crate) use worker_env::{
|
|
364
376
|
apply_copilot_instructions_overlay, apply_cursor_agent_rules_overlay,
|
|
365
377
|
apply_cursor_subscription_proxy_env_with_profile, apply_mcp_auto_approval_env,
|
|
366
|
-
apply_profile_launch_env, auth_mode_env_value,
|
|
367
|
-
|
|
368
|
-
|
|
378
|
+
apply_profile_launch_env, auth_mode_env_value, fill_spawn_placeholders,
|
|
379
|
+
fill_spawn_placeholders_full, inherited_env_with_team_overrides, persist_command_plan_state,
|
|
380
|
+
spawn_timestamp,
|
|
369
381
|
};
|
|
370
382
|
pub use worker_env::{apply_cursor_subscription_proxy_env, CursorProxyPresence};
|
|
371
383
|
|
|
@@ -393,18 +405,18 @@ pub(crate) use quick_start_transport::{
|
|
|
393
405
|
};
|
|
394
406
|
|
|
395
407
|
pub mod readiness;
|
|
408
|
+
pub(super) use readiness::*;
|
|
396
409
|
pub use readiness::{
|
|
397
410
|
classify_leader_binding, launched_team_receiver_is_attached, selected_team_leader_receiver,
|
|
398
411
|
LeaderBindingClass,
|
|
399
412
|
};
|
|
400
|
-
pub(super) use readiness::*;
|
|
401
413
|
|
|
402
414
|
mod add_agent;
|
|
403
415
|
pub(super) use add_agent::*;
|
|
404
416
|
pub use add_agent::{add_agent, add_agent_force};
|
|
405
417
|
pub(crate) use add_agent::{
|
|
406
|
-
add_agent_with_transport,
|
|
407
|
-
|
|
418
|
+
add_agent_with_transport, add_agent_with_transport_at_paths_locked,
|
|
419
|
+
add_agent_with_transport_force, force_recreate_with_transport_locked,
|
|
408
420
|
};
|
|
409
421
|
|
|
410
422
|
pub(crate) mod add_agent_state;
|
|
@@ -428,21 +440,20 @@ pub(super) use ownership::*;
|
|
|
428
440
|
pub(crate) use ownership::{ensure_owner_allowed, ensure_owner_allowed_for_state, state_path};
|
|
429
441
|
|
|
430
442
|
pub mod spec_state;
|
|
431
|
-
pub use spec_state::{
|
|
432
|
-
seed_launched_owner_from_caller_with_provider_lookup, worker_session_name_pub,
|
|
433
|
-
};
|
|
434
443
|
pub(crate) use spec_state::{
|
|
435
444
|
effective_runtime_config_for_worker_spawn, effective_runtime_config_for_worker_spawn_json,
|
|
436
445
|
};
|
|
437
446
|
use spec_state::{
|
|
438
|
-
env_nonempty, has_positive_caller_leader_env, initial_runtime_state,
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
spec_tasks_json, team_workspace, yaml_value_to_json,
|
|
447
|
+
env_nonempty, has_positive_caller_leader_env, initial_runtime_state, override_spec_runtime_str,
|
|
448
|
+
seed_launched_owner_from_env, spec_agent_values, spec_agents, spec_default_assignee,
|
|
449
|
+
spec_routes, spec_session_name, spec_tasks_json, team_workspace, yaml_value_to_json,
|
|
442
450
|
};
|
|
443
451
|
pub(crate) use spec_state::{
|
|
444
452
|
override_spec_session_name, override_spec_workspace, spec_agent_id_set, write_spec_atomic,
|
|
445
453
|
};
|
|
454
|
+
pub use spec_state::{
|
|
455
|
+
seed_launched_owner_from_caller_with_provider_lookup, worker_session_name_pub,
|
|
456
|
+
};
|
|
446
457
|
|
|
447
458
|
#[cfg(test)]
|
|
448
459
|
mod tests {
|
|
@@ -31,6 +31,7 @@ use crate::lifecycle::LifecycleError;
|
|
|
31
31
|
use crate::model::enums::{AuthMode, Provider};
|
|
32
32
|
use crate::model::yaml::Value as YamlValue;
|
|
33
33
|
use crate::provider::{McpConfig, ProviderCommandOverrides, ProviderProfileLaunch};
|
|
34
|
+
use crate::provider::command_helpers::append_env_pair;
|
|
34
35
|
|
|
35
36
|
const COMPATIBLE_NETWORK_ENV_KEYS: &[&str] = &[
|
|
36
37
|
"HTTPS_PROXY",
|
|
@@ -463,40 +464,26 @@ fn provider_env_exports(
|
|
|
463
464
|
}
|
|
464
465
|
match provider {
|
|
465
466
|
Provider::Claude | Provider::ClaudeCode => {
|
|
466
|
-
|
|
467
|
-
exports.insert("ANTHROPIC_BASE_URL".to_string(), value.to_string());
|
|
468
|
-
}
|
|
467
|
+
append_env_pair(&mut exports, "ANTHROPIC_BASE_URL", value_or_alternate(values, "ANTHROPIC_BASE_URL", "BASE_URL"));
|
|
469
468
|
if auth_mode == AuthMode::OfficialApi {
|
|
470
|
-
|
|
471
|
-
exports.insert("ANTHROPIC_API_KEY".to_string(), value.to_string());
|
|
472
|
-
}
|
|
469
|
+
append_env_pair(&mut exports, "ANTHROPIC_API_KEY", value_or_alternate(values, "ANTHROPIC_API_KEY", "API_KEY"));
|
|
473
470
|
}
|
|
474
|
-
|
|
471
|
+
append_env_pair(&mut exports, "ANTHROPIC_AUTH_TOKEN", value_or_alternate(values, "ANTHROPIC_AUTH_TOKEN", "AUTH_TOKEN")
|
|
475
472
|
.or_else(|| {
|
|
476
473
|
(auth_mode == AuthMode::CompatibleApi)
|
|
477
474
|
.then(|| value_or_alternate(values, "ANTHROPIC_API_KEY", "API_KEY"))
|
|
478
475
|
.flatten()
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
exports.insert("ANTHROPIC_AUTH_TOKEN".to_string(), value.to_string());
|
|
482
|
-
}
|
|
483
|
-
if let Some(value) = profile_model(values) {
|
|
484
|
-
exports.insert("ANTHROPIC_MODEL".to_string(), value.to_string());
|
|
485
|
-
}
|
|
476
|
+
}));
|
|
477
|
+
append_env_pair(&mut exports, "ANTHROPIC_MODEL", profile_model(values));
|
|
486
478
|
}
|
|
487
479
|
Provider::Codex => {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
if let Some(value) = values.get("BASE_URL").filter(|value| !value.is_empty()) {
|
|
493
|
-
exports.insert("OPENAI_BASE_URL".to_string(), value.to_string());
|
|
494
|
-
}
|
|
480
|
+
let key = value_or_alternate(values, "OPENAI_API_KEY", "API_KEY");
|
|
481
|
+
append_env_pair(&mut exports, "TEAM_AGENT_PROVIDER_API_KEY", key);
|
|
482
|
+
append_env_pair(&mut exports, "OPENAI_API_KEY", key);
|
|
483
|
+
append_env_pair(&mut exports, "OPENAI_BASE_URL", values.get("BASE_URL").filter(|value| !value.is_empty()).map(String::as_str));
|
|
495
484
|
}
|
|
496
485
|
Provider::GeminiCli => {
|
|
497
|
-
|
|
498
|
-
exports.insert("GEMINI_API_KEY".to_string(), value.to_string());
|
|
499
|
-
}
|
|
486
|
+
append_env_pair(&mut exports, "GEMINI_API_KEY", value_or_alternate(values, "GEMINI_API_KEY", "API_KEY"));
|
|
500
487
|
}
|
|
501
488
|
// C-A-4 cr verdict v2 — copilot BYOK(== compatible_api 档,help-providers 原文
|
|
502
489
|
// "GitHub authentication is not required" when COPILOT_PROVIDER_BASE_URL set):
|
|
@@ -506,28 +493,14 @@ fn provider_env_exports(
|
|
|
506
493
|
// Subscription/OfficialApi 不导出 COPILOT_PROVIDER_*(避免误改 auth 通道)。
|
|
507
494
|
Provider::Copilot => {
|
|
508
495
|
if auth_mode == AuthMode::CompatibleApi {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
exports.insert("COPILOT_PROVIDER_TYPE".to_string(), value.to_string());
|
|
518
|
-
}
|
|
519
|
-
if let Some(value) =
|
|
520
|
-
value_or_alternate(values, "COPILOT_PROVIDER_API_KEY", "API_KEY")
|
|
521
|
-
{
|
|
522
|
-
exports.insert("COPILOT_PROVIDER_API_KEY".to_string(), value.to_string());
|
|
523
|
-
}
|
|
524
|
-
if let Some(value) =
|
|
525
|
-
value_or_alternate(values, "COPILOT_PROVIDER_WIRE_API", "WIRE_API")
|
|
526
|
-
{
|
|
527
|
-
exports.insert("COPILOT_PROVIDER_WIRE_API".to_string(), value.to_string());
|
|
528
|
-
}
|
|
529
|
-
if let Some(value) = value_or_alternate(values, "COPILOT_MODEL", "MODEL") {
|
|
530
|
-
exports.insert("COPILOT_MODEL".to_string(), value.to_string());
|
|
496
|
+
for (key, alternate) in [
|
|
497
|
+
("COPILOT_PROVIDER_BASE_URL", "BASE_URL"),
|
|
498
|
+
("COPILOT_PROVIDER_TYPE", "PROVIDER_TYPE"),
|
|
499
|
+
("COPILOT_PROVIDER_API_KEY", "API_KEY"),
|
|
500
|
+
("COPILOT_PROVIDER_WIRE_API", "WIRE_API"),
|
|
501
|
+
("COPILOT_MODEL", "MODEL"),
|
|
502
|
+
] {
|
|
503
|
+
append_env_pair(&mut exports, key, value_or_alternate(values, key, alternate));
|
|
531
504
|
}
|
|
532
505
|
}
|
|
533
506
|
}
|
|
@@ -539,6 +512,45 @@ fn provider_env_exports(
|
|
|
539
512
|
exports
|
|
540
513
|
}
|
|
541
514
|
|
|
515
|
+
#[cfg(test)]
|
|
516
|
+
mod append_env_contract {
|
|
517
|
+
use super::*;
|
|
518
|
+
|
|
519
|
+
#[test]
|
|
520
|
+
fn profile_append_keeps_native_keys_alternates_and_auth_gates() {
|
|
521
|
+
let values: BTreeMap<String, String> = [
|
|
522
|
+
("API_KEY", "dummy"), ("BASE_URL", " raw\nbase "),
|
|
523
|
+
("AUTH_TOKEN", "dummy-token"), ("MODEL", " raw model "),
|
|
524
|
+
("PROVIDER_TYPE", "custom"), ("WIRE_API", "responses"),
|
|
525
|
+
("OPENAI_API_KEY", "native-dummy"),
|
|
526
|
+
].into_iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
|
|
527
|
+
let map = |pairs: &[(&str, &str)]| -> BTreeMap<String, String> {
|
|
528
|
+
pairs.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()
|
|
529
|
+
};
|
|
530
|
+
for provider in [Provider::Claude, Provider::ClaudeCode] {
|
|
531
|
+
assert_eq!(provider_env_exports(provider, AuthMode::OfficialApi, &values), map(&[
|
|
532
|
+
("ANTHROPIC_BASE_URL", " raw\nbase "), ("ANTHROPIC_API_KEY", "dummy"),
|
|
533
|
+
("ANTHROPIC_AUTH_TOKEN", "dummy-token"), ("ANTHROPIC_MODEL", " raw model "),
|
|
534
|
+
]));
|
|
535
|
+
}
|
|
536
|
+
assert_eq!(provider_env_exports(Provider::Codex, AuthMode::CompatibleApi, &values), map(&[
|
|
537
|
+
("TEAM_AGENT_PROVIDER_API_KEY", "native-dummy"), ("OPENAI_API_KEY", "native-dummy"),
|
|
538
|
+
("OPENAI_BASE_URL", " raw\nbase "),
|
|
539
|
+
]));
|
|
540
|
+
assert_eq!(provider_env_exports(Provider::GeminiCli, AuthMode::OfficialApi, &values), map(&[("GEMINI_API_KEY", "dummy")]));
|
|
541
|
+
assert_eq!(provider_env_exports(Provider::Copilot, AuthMode::CompatibleApi, &values), map(&[
|
|
542
|
+
("COPILOT_PROVIDER_BASE_URL", " raw\nbase "), ("COPILOT_PROVIDER_TYPE", "custom"),
|
|
543
|
+
("COPILOT_PROVIDER_API_KEY", "dummy"), ("COPILOT_PROVIDER_WIRE_API", "responses"),
|
|
544
|
+
("COPILOT_MODEL", " raw model "),
|
|
545
|
+
]));
|
|
546
|
+
assert!(provider_env_exports(Provider::Copilot, AuthMode::OfficialApi, &values).is_empty());
|
|
547
|
+
for provider in crate::provider::wire::all_providers() {
|
|
548
|
+
assert!(provider_env_exports(*provider, AuthMode::Subscription, &values).is_empty());
|
|
549
|
+
assert!(provider_env_exports(*provider, AuthMode::CompatibleApi, &BTreeMap::new()).is_empty());
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
542
554
|
/// ---
|
|
543
555
|
/// purpose: 给出该 provider 与 auth_mode 下必须从子进程环境里清掉的变量名集
|
|
544
556
|
/// returns: 变量名集合;claude 系清掉会让子进程并入父会话的那组标识
|
|
@@ -41,6 +41,71 @@ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest}
|
|
|
41
41
|
// `pane_verify_ms`, `startup_prompt_handler_ms`, `tmux_start_mode`) so
|
|
42
42
|
// bounded-concurrency spawn can be justified with real numbers.
|
|
43
43
|
|
|
44
|
+
/// Stable wire labels for launch/restart timing events, not readiness states.
|
|
45
|
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
46
|
+
pub(crate) enum LifecyclePhase {
|
|
47
|
+
ResolveContext,
|
|
48
|
+
CompileSpec,
|
|
49
|
+
PlanClassification,
|
|
50
|
+
Teardown,
|
|
51
|
+
SpawnAll,
|
|
52
|
+
SaveState,
|
|
53
|
+
CoordinatorStart,
|
|
54
|
+
ReadinessWait,
|
|
55
|
+
Completed,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
impl LifecyclePhase {
|
|
59
|
+
pub(crate) fn as_str(self) -> &'static str {
|
|
60
|
+
match self {
|
|
61
|
+
Self::ResolveContext => "resolve_context",
|
|
62
|
+
Self::CompileSpec => "compile_spec",
|
|
63
|
+
Self::PlanClassification => "plan_classification",
|
|
64
|
+
Self::Teardown => "teardown",
|
|
65
|
+
Self::SpawnAll => "spawn_all",
|
|
66
|
+
Self::SaveState => "save_state",
|
|
67
|
+
Self::CoordinatorStart => "coordinator_start",
|
|
68
|
+
Self::ReadinessWait => "readiness_wait",
|
|
69
|
+
Self::Completed => "completed",
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Pure event formatting shared by launch/restart; no clock, I/O or readiness policy.
|
|
74
|
+
pub(crate) fn event_fields(self, elapsed_ms: u64) -> serde_json::Value {
|
|
75
|
+
serde_json::json!({
|
|
76
|
+
"phase": self.as_str(),
|
|
77
|
+
"elapsed_ms": elapsed_ms,
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#[test]
|
|
83
|
+
fn lifecycle_phase_wire_labels_remain_stable() {
|
|
84
|
+
for (phase, wire) in [
|
|
85
|
+
(LifecyclePhase::ResolveContext, "resolve_context"),
|
|
86
|
+
(LifecyclePhase::CompileSpec, "compile_spec"),
|
|
87
|
+
(LifecyclePhase::PlanClassification, "plan_classification"),
|
|
88
|
+
(LifecyclePhase::Teardown, "teardown"),
|
|
89
|
+
(LifecyclePhase::SpawnAll, "spawn_all"),
|
|
90
|
+
(LifecyclePhase::SaveState, "save_state"),
|
|
91
|
+
(LifecyclePhase::CoordinatorStart, "coordinator_start"),
|
|
92
|
+
(LifecyclePhase::ReadinessWait, "readiness_wait"),
|
|
93
|
+
(LifecyclePhase::Completed, "completed"),
|
|
94
|
+
] {
|
|
95
|
+
assert_eq!(phase.as_str(), wire);
|
|
96
|
+
for elapsed_ms in [0, 123, u64::MAX] {
|
|
97
|
+
assert_eq!(phase.event_fields(elapsed_ms), serde_json::json!({
|
|
98
|
+
"phase": wire,
|
|
99
|
+
"elapsed_ms": elapsed_ms,
|
|
100
|
+
}));
|
|
101
|
+
assert_eq!(
|
|
102
|
+
serde_json::to_string(&phase.event_fields(elapsed_ms)).expect("phase JSON"),
|
|
103
|
+
format!("{{\"phase\":\"{wire}\",\"elapsed_ms\":{elapsed_ms}}}"),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
44
109
|
pub(crate) struct RestartPhaseTimer {
|
|
45
110
|
started_at: std::time::Instant,
|
|
46
111
|
}
|
|
@@ -71,15 +136,9 @@ impl RestartPhaseTimer {
|
|
|
71
136
|
/// phase: 阶段名
|
|
72
137
|
/// returns: 写事件失败被吞掉,不影响主流程
|
|
73
138
|
/// ---
|
|
74
|
-
pub(crate) fn emit(&self, workspace: &Path, kind: &'static str, phase:
|
|
139
|
+
pub(crate) fn emit(&self, workspace: &Path, kind: &'static str, phase: LifecyclePhase) {
|
|
75
140
|
let event_log = crate::event_log::EventLog::new(workspace);
|
|
76
|
-
let _ = event_log.write(
|
|
77
|
-
kind,
|
|
78
|
-
serde_json::json!({
|
|
79
|
-
"phase": phase,
|
|
80
|
-
"elapsed_ms": self.elapsed_ms(),
|
|
81
|
-
}),
|
|
82
|
-
);
|
|
141
|
+
let _ = event_log.write(kind, phase.event_fields(self.elapsed_ms()));
|
|
83
142
|
}
|
|
84
143
|
}
|
|
85
144
|
|
|
@@ -305,7 +364,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
305
364
|
// resolved a context; downstream phases are emitted with monotonic
|
|
306
365
|
// `elapsed_ms` for at-a-glance latency triage.
|
|
307
366
|
let phase_timer = RestartPhaseTimer::start();
|
|
308
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
367
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::ResolveContext);
|
|
309
368
|
let lifecycle_lock = acquire_agent_lifecycle_lock(LifecycleLockRequest {
|
|
310
369
|
workspace: &selected.run_workspace,
|
|
311
370
|
operation: "restart",
|
|
@@ -345,7 +404,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
345
404
|
// 写 runtime spec。角色缺(TEAM.md/agents 不在)→ 显式拒(列缺哪些),旧 spec 原地保留不删不用。
|
|
346
405
|
let spec =
|
|
347
406
|
rebuild_runtime_spec_from_roles(&selected.run_workspace, &selected.team_key, &state)?;
|
|
348
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
407
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::CompileSpec);
|
|
349
408
|
// 重建后 spec_workspace 恒为 runtime spec 的父目录(.team/runtime/<team_key>/)。
|
|
350
409
|
let runtime_spec =
|
|
351
410
|
crate::model::paths::runtime_spec_path(&selected.run_workspace, &selected.team_key);
|
|
@@ -441,7 +500,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
441
500
|
phase_timer.emit(
|
|
442
501
|
&selected.run_workspace,
|
|
443
502
|
"restart.phase",
|
|
444
|
-
|
|
503
|
+
LifecyclePhase::PlanClassification,
|
|
445
504
|
);
|
|
446
505
|
write_restart_resume_decision_events(
|
|
447
506
|
&selected.run_workspace,
|
|
@@ -558,8 +617,8 @@ fn restart_with_selected_team_and_transport(
|
|
|
558
617
|
&topology_authority_agent_ids,
|
|
559
618
|
)?;
|
|
560
619
|
}
|
|
561
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
562
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
620
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::Teardown);
|
|
621
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::SpawnAll);
|
|
563
622
|
// 0.5.40 Slice 3 (tmux-server-death-locate §7 Slice 3): when the
|
|
564
623
|
// pre-spawn teardown was DEFERRED (live worker session with
|
|
565
624
|
// authoritative running agents), snapshot the pre-spawn agent rows
|
|
@@ -582,8 +641,8 @@ fn restart_with_selected_team_and_transport(
|
|
|
582
641
|
// tmux session is created deterministically; the remaining independent
|
|
583
642
|
// decisions run their `transport.spawn_into` in parallel via a thread
|
|
584
643
|
// scope with concurrency capped at `min(4, workers-1)`. Every spawn
|
|
585
|
-
// result is collected in-memory; `
|
|
586
|
-
//
|
|
644
|
+
// result is collected in-memory; `mark_agent_respawned` is then
|
|
645
|
+
// applied in ORIGINAL PLAN ORDER so
|
|
587
646
|
// persisted `spawn_epoch` / `spawned_at` / `pane_id` / `window` stay
|
|
588
647
|
// deterministic regardless of thread completion order. Failure
|
|
589
648
|
// aggregation stays equivalent to serial: per-agent errors go into
|
|
@@ -837,17 +896,14 @@ fn restart_with_selected_team_and_transport(
|
|
|
837
896
|
agent.get("dangerously_skip_permissions"),
|
|
838
897
|
Some(serde_json::Value::Bool(true))
|
|
839
898
|
);
|
|
840
|
-
if let Err(error) =
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
launched_bypass,
|
|
849
|
-
)
|
|
850
|
-
})
|
|
899
|
+
if let Err(error) = mark_agent_respawned(
|
|
900
|
+
&mut state,
|
|
901
|
+
&decision.agent_id,
|
|
902
|
+
decision.restart_mode,
|
|
903
|
+
&spawn,
|
|
904
|
+
transport,
|
|
905
|
+
launched_bypass,
|
|
906
|
+
)
|
|
851
907
|
{
|
|
852
908
|
let error = error.to_string();
|
|
853
909
|
mark_agent_restart_failed(&mut state, decision, &error);
|
|
@@ -1026,7 +1082,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
1026
1082
|
&capture_backfill_skip_agent_ids,
|
|
1027
1083
|
&topology_authority_agent_ids,
|
|
1028
1084
|
)?;
|
|
1029
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
1085
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::SaveState);
|
|
1030
1086
|
if fatal_resume_failure {
|
|
1031
1087
|
let attach_commands = Vec::new();
|
|
1032
1088
|
let next_actions = restart_failure_next_actions(&failed_agents);
|
|
@@ -1304,10 +1360,10 @@ fn restart_with_selected_team_and_transport(
|
|
|
1304
1360
|
phase_timer.emit(
|
|
1305
1361
|
&selected.run_workspace,
|
|
1306
1362
|
"restart.phase",
|
|
1307
|
-
|
|
1363
|
+
LifecyclePhase::CoordinatorStart,
|
|
1308
1364
|
);
|
|
1309
1365
|
let coordinator_started = coordinator.ok;
|
|
1310
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
1366
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::ReadinessWait);
|
|
1311
1367
|
wait_restart_readiness_or_timeout(
|
|
1312
1368
|
&selected.run_workspace,
|
|
1313
1369
|
&state,
|
|
@@ -1331,7 +1387,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
1331
1387
|
let mut next_actions = Vec::new();
|
|
1332
1388
|
if !failed_agents.is_empty() {
|
|
1333
1389
|
next_actions.extend(restart_failure_next_actions(&failed_agents));
|
|
1334
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
1390
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::Completed);
|
|
1335
1391
|
write_restart_completed_event(
|
|
1336
1392
|
&selected.run_workspace,
|
|
1337
1393
|
&successful_agents,
|
|
@@ -1360,7 +1416,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
1360
1416
|
leader_bind_reason,
|
|
1361
1417
|
});
|
|
1362
1418
|
}
|
|
1363
|
-
phase_timer.emit(&selected.run_workspace, "restart.phase",
|
|
1419
|
+
phase_timer.emit(&selected.run_workspace, "restart.phase", LifecyclePhase::Completed);
|
|
1364
1420
|
write_restart_completed_event(
|
|
1365
1421
|
&selected.run_workspace,
|
|
1366
1422
|
&successful_agents,
|
|
@@ -1852,14 +1908,6 @@ fn yes_no(value: bool) -> &'static str {
|
|
|
1852
1908
|
}
|
|
1853
1909
|
}
|
|
1854
1910
|
|
|
1855
|
-
fn verify_spawned_agent_live(
|
|
1856
|
-
_agent_id: &AgentId,
|
|
1857
|
-
_spawn: &SpawnedAgentWindow,
|
|
1858
|
-
_transport: &dyn crate::transport::Transport,
|
|
1859
|
-
) -> Result<(), LifecycleError> {
|
|
1860
|
-
Ok(())
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
1911
|
/// 0.3.30 Bug 1: restart success path auto-attach.
|
|
1864
1912
|
/// When `TMUX_PANE` is present in the caller's env, treat the restart as if
|
|
1865
1913
|
/// the user had also run `attach-leader` from that pane. Mirrors quick-start's
|
|
@@ -3088,8 +3136,8 @@ fn run_bounded_parallel_worker_spawns(
|
|
|
3088
3136
|
outcomes
|
|
3089
3137
|
}
|
|
3090
3138
|
|
|
3091
|
-
/// 0.5.38 Step 2: serial post-spawn stage. Runs
|
|
3092
|
-
///
|
|
3139
|
+
/// 0.5.38 Step 2: serial post-spawn stage. Runs mark_agent_respawned
|
|
3140
|
+
/// in plan order, emits worker.spawn_timing, and
|
|
3093
3141
|
/// updates the successful/failed agent lists exactly like the pre-0.5.38
|
|
3094
3142
|
/// serial loop. Keeping this serial guarantees deterministic persisted
|
|
3095
3143
|
/// state (`spawn_epoch`, `spawned_at`, etc.) regardless of parallel
|
|
@@ -3111,17 +3159,14 @@ fn apply_marked_respawn(
|
|
|
3111
3159
|
launched_bypass: bool,
|
|
3112
3160
|
) {
|
|
3113
3161
|
let verify_start = std::time::Instant::now();
|
|
3114
|
-
if let Err(error) =
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
launched_bypass,
|
|
3123
|
-
)
|
|
3124
|
-
})
|
|
3162
|
+
if let Err(error) = mark_agent_respawned(
|
|
3163
|
+
state,
|
|
3164
|
+
&decision.agent_id,
|
|
3165
|
+
decision.restart_mode,
|
|
3166
|
+
spawn,
|
|
3167
|
+
transport,
|
|
3168
|
+
launched_bypass,
|
|
3169
|
+
)
|
|
3125
3170
|
{
|
|
3126
3171
|
let error = error.to_string();
|
|
3127
3172
|
mark_agent_restart_failed(state, decision, &error);
|