@team-agent/installer 0.5.66 → 0.5.69
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 +8 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +1 -0
- package/crates/team-agent/src/cli/adapters.rs +5 -0
- package/crates/team-agent/src/cli/diagnose.rs +156 -0
- package/crates/team-agent/src/cli/emit.rs +6 -5
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +22 -7
- package/crates/team-agent/src/cli/leaders.rs +125 -1
- package/crates/team-agent/src/cli/mod.rs +20 -0
- package/crates/team-agent/src/cli/send/presentation.rs +7 -1
- package/crates/team-agent/src/cli/spec.rs +3 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
- package/crates/team-agent/src/cli/tests/base.rs +26 -3
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
- package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
- package/crates/team-agent/src/communication_mode/mod.rs +6 -4
- package/crates/team-agent/src/compiler.rs +121 -5
- package/crates/team-agent/src/coordinator/backoff.rs +55 -0
- package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
- package/crates/team-agent/src/coordinator/health.rs +173 -0
- package/crates/team-agent/src/coordinator/mod.rs +31 -0
- package/crates/team-agent/src/coordinator/orphan.rs +31 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +31 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
- package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
- package/crates/team-agent/src/coordinator/tick.rs +133 -22
- package/crates/team-agent/src/coordinator/types.rs +49 -0
- package/crates/team-agent/src/db/message_store.rs +39 -5
- package/crates/team-agent/src/layout/worker_env.rs +20 -3
- package/crates/team-agent/src/layout/worker_window_helpers.rs +3 -0
- package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
- package/crates/team-agent/src/leader/registry.rs +65 -0
- package/crates/team-agent/src/leader/start.rs +973 -75
- package/crates/team-agent/src/lifecycle/display.rs +56 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +398 -26
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +80 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +60 -0
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
- package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +548 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +120 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +208 -456
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
- package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +482 -1
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +830 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +87 -3
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +127 -25
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +140 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
- package/crates/team-agent/src/lifecycle/launch.rs +107 -29
- package/crates/team-agent/src/lifecycle/lock.rs +42 -1
- package/crates/team-agent/src/lifecycle/mod.rs +11 -0
- package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +99 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +52 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +214 -160
- package/crates/team-agent/src/lifecycle/restart/common.rs +513 -37
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +233 -6
- package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
- package/crates/team-agent/src/lifecycle/restart.rs +29 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +24 -3
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +464 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +247 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_g0.stdout.txt +222 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +787 -0
- package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +485 -0
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
- package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
- package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +97 -0
- package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +128 -0
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +207 -0
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +838 -0
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +320 -0
- package/crates/team-agent/src/lifecycle/tests/pi_lifecycle_doctor_red.rs +98 -0
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +216 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
- package/crates/team-agent/src/lifecycle/tests.rs +30 -13
- package/crates/team-agent/src/lifecycle/types.rs +61 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +143 -12
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
- package/crates/team-agent/src/mcp_server/tests.rs +196 -2
- package/crates/team-agent/src/messaging/delivery.rs +430 -41
- package/crates/team-agent/src/messaging/helpers.rs +2 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
- package/crates/team-agent/src/messaging/results.rs +4 -0
- package/crates/team-agent/src/messaging/send.rs +34 -0
- package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
- package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/types.rs +4 -1
- package/crates/team-agent/src/model/enums.rs +17 -6
- package/crates/team-agent/src/model/permissions.rs +3 -0
- package/crates/team-agent/src/os_probe.rs +73 -2
- package/crates/team-agent/src/provider/adapter.rs +259 -6
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +89 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +3 -0
- package/crates/team-agent/src/provider/adapters/pi.rs +152 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +47 -2
- package/crates/team-agent/src/provider/classify.rs +4 -2
- package/crates/team-agent/src/provider/faults.rs +2 -1
- package/crates/team-agent/src/provider/mod.rs +11 -0
- package/crates/team-agent/src/provider/session/capture.rs +158 -20
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
- package/crates/team-agent/src/provider/session/context_fork.rs +78 -3
- package/crates/team-agent/src/provider/session/mod.rs +18 -0
- package/crates/team-agent/src/provider/session/resume.rs +57 -0
- package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
- package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
- package/crates/team-agent/src/provider/session_scan/common.rs +205 -2
- package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
- package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
- package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
- package/crates/team-agent/src/provider/session_scan/pi.rs +172 -0
- package/crates/team-agent/src/provider/session_scan.rs +12 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +384 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +29 -1
- package/crates/team-agent/src/state/persist.rs +301 -8
- package/crates/team-agent/src/state/repository.rs +5 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
- package/crates/team-agent/src/tmux_backend.rs +1116 -75
- package/crates/team-agent/src/transport/tests/wire.rs +28 -2
- package/crates/team-agent/src/transport.rs +248 -1
- package/npm/install.mjs +129 -73
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +32 -239
- package/skills/team-agent/command-coverage.json +31 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
- package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
|
@@ -1,6 +1,32 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 动态角色文档加一席,失败按快照回滚;force 变体先摘旧席再加
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: add_agent
|
|
6
|
+
//! what: 编译角色进 runtime spec 并起席,失败回滚 spec 与 state
|
|
7
|
+
//! - name: add_agent_force
|
|
8
|
+
//! what: 先快照并摘除同名旧席,再走正常加席,失败按快照恢复
|
|
9
|
+
//! - name: add_agent_with_transport_at_paths
|
|
10
|
+
//! what: 加席的实体实现,含 owner 门、重名拒绝、原子写 spec 与起席
|
|
11
|
+
//! depends:
|
|
12
|
+
//! - crate::lifecycle::lock
|
|
13
|
+
//! - crate::lifecycle::restart
|
|
14
|
+
//! - crate::lifecycle::restart::remove
|
|
15
|
+
//! - crate::compiler
|
|
16
|
+
//! - crate::state::selector
|
|
17
|
+
//! - crate::state::projection
|
|
18
|
+
//! - crate::state::persist
|
|
19
|
+
//! - crate::tmux_backend
|
|
20
|
+
//! boundary:
|
|
21
|
+
//! - 不拷贝外部角色文件进 team 目录,就地读取编译
|
|
22
|
+
//! - 起席一律走 restart 的 start_agent_at_paths,本文件不直接 spawn
|
|
23
|
+
//! - start path 按 exact pane receipt 回收已 spawn pane;本文件恢复 spec/state,并删除新 Pi 席位的 exact wrapper
|
|
24
|
+
//! maturity: wired
|
|
25
|
+
//! ---
|
|
1
26
|
use std::collections::{BTreeMap, BTreeSet};
|
|
2
27
|
use std::path::{Path, PathBuf};
|
|
3
28
|
use std::process::Command;
|
|
29
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
4
30
|
|
|
5
31
|
use crate::lifecycle::*;
|
|
6
32
|
use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
|
|
@@ -14,6 +40,165 @@ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest}
|
|
|
14
40
|
|
|
15
41
|
use super::*;
|
|
16
42
|
|
|
43
|
+
struct AgentReservation {
|
|
44
|
+
workspace: PathBuf,
|
|
45
|
+
team_key: String,
|
|
46
|
+
agent_id: AgentId,
|
|
47
|
+
owner: String,
|
|
48
|
+
active: bool,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
impl AgentReservation {
|
|
52
|
+
fn commit(mut self) {
|
|
53
|
+
self.active = false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
impl Drop for AgentReservation {
|
|
58
|
+
fn drop(&mut self) {
|
|
59
|
+
if !self.active {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
let request = LifecycleLockRequest {
|
|
63
|
+
workspace: &self.workspace,
|
|
64
|
+
operation: "agent-reservation-rollback",
|
|
65
|
+
team: Some(self.team_key.as_str()),
|
|
66
|
+
agent_id: Some(&self.agent_id),
|
|
67
|
+
};
|
|
68
|
+
let Ok(_lock) = acquire_agent_lifecycle_lock(request) else {
|
|
69
|
+
let _ = crate::event_log::EventLog::new(&self.workspace).write(
|
|
70
|
+
"lifecycle.clone_reservation_rollback_failed",
|
|
71
|
+
serde_json::json!({
|
|
72
|
+
"agent_id": self.agent_id.as_str(),
|
|
73
|
+
"reservation_owner": self.owner,
|
|
74
|
+
"reason": "lifecycle lock unavailable",
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
return;
|
|
78
|
+
};
|
|
79
|
+
let result =
|
|
80
|
+
release_agent_reservation(&self.workspace, &self.team_key, &self.agent_id, &self.owner);
|
|
81
|
+
let _ = crate::event_log::EventLog::new(&self.workspace).write(
|
|
82
|
+
if result.is_ok() {
|
|
83
|
+
"lifecycle.clone_reservation_rolled_back"
|
|
84
|
+
} else {
|
|
85
|
+
"lifecycle.clone_reservation_rollback_failed"
|
|
86
|
+
},
|
|
87
|
+
serde_json::json!({
|
|
88
|
+
"agent_id": self.agent_id.as_str(),
|
|
89
|
+
"reservation_owner": self.owner,
|
|
90
|
+
"result": result.as_ref().err().map(ToString::to_string),
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn reserve_agent_slot(
|
|
97
|
+
workspace: &Path,
|
|
98
|
+
team: Option<&str>,
|
|
99
|
+
agent_id: &AgentId,
|
|
100
|
+
) -> Result<AgentReservation, LifecycleError> {
|
|
101
|
+
let state = crate::state::projection::select_runtime_state(workspace, team)
|
|
102
|
+
.map_err(|error| LifecycleError::TeamSelect(error.to_string()))?;
|
|
103
|
+
let team_key = team
|
|
104
|
+
.filter(|key| !key.is_empty())
|
|
105
|
+
.map(str::to_string)
|
|
106
|
+
.or_else(|| explicit_active_team_key(&state))
|
|
107
|
+
.unwrap_or_else(|| crate::state::projection::team_state_key(&state));
|
|
108
|
+
let owner = format!(
|
|
109
|
+
"{}-{}",
|
|
110
|
+
std::process::id(),
|
|
111
|
+
SystemTime::now()
|
|
112
|
+
.duration_since(UNIX_EPOCH)
|
|
113
|
+
.map_err(|error| LifecycleError::StatePersist(error.to_string()))?
|
|
114
|
+
.as_nanos()
|
|
115
|
+
);
|
|
116
|
+
let mut latest = crate::state::projection::select_runtime_state(workspace, Some(&team_key))
|
|
117
|
+
.map_err(|error| LifecycleError::TeamSelect(error.to_string()))?;
|
|
118
|
+
ensure_owner_allowed_for_state(&latest, Some(agent_id))?;
|
|
119
|
+
if runtime_agent_exists(&latest, agent_id) {
|
|
120
|
+
return Err(LifecycleError::RequirementUnmet(format!(
|
|
121
|
+
"agent id already exists: {agent_id}"
|
|
122
|
+
)));
|
|
123
|
+
}
|
|
124
|
+
latest
|
|
125
|
+
.as_object_mut()
|
|
126
|
+
.ok_or_else(|| LifecycleError::StatePersist("runtime state root is not an object".into()))?
|
|
127
|
+
.entry("agents")
|
|
128
|
+
.or_insert_with(|| serde_json::json!({}));
|
|
129
|
+
let agents = latest
|
|
130
|
+
.get_mut("agents")
|
|
131
|
+
.and_then(serde_json::Value::as_object_mut)
|
|
132
|
+
.ok_or_else(|| {
|
|
133
|
+
LifecycleError::StatePersist("runtime state agents is not an object".into())
|
|
134
|
+
})?;
|
|
135
|
+
agents.insert(
|
|
136
|
+
agent_id.as_str().to_string(),
|
|
137
|
+
serde_json::json!({
|
|
138
|
+
"agent_id": agent_id.as_str(),
|
|
139
|
+
"status": "reserved",
|
|
140
|
+
"reservation_owner": owner,
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
save_launched_team_state_for_key(
|
|
144
|
+
workspace,
|
|
145
|
+
&latest,
|
|
146
|
+
Some(team_key.as_str()),
|
|
147
|
+
Some(agent_id.as_str()),
|
|
148
|
+
)?;
|
|
149
|
+
let _ = crate::event_log::EventLog::new(workspace).write(
|
|
150
|
+
"lifecycle.clone_reservation_acquired",
|
|
151
|
+
serde_json::json!({
|
|
152
|
+
"agent_id": agent_id.as_str(),
|
|
153
|
+
"reservation_owner": owner,
|
|
154
|
+
"team": team_key,
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
Ok(AgentReservation {
|
|
158
|
+
workspace: workspace.to_path_buf(),
|
|
159
|
+
team_key,
|
|
160
|
+
agent_id: agent_id.clone(),
|
|
161
|
+
owner,
|
|
162
|
+
active: true,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn release_agent_reservation(
|
|
167
|
+
workspace: &Path,
|
|
168
|
+
team_key: &str,
|
|
169
|
+
agent_id: &AgentId,
|
|
170
|
+
owner: &str,
|
|
171
|
+
) -> Result<(), LifecycleError> {
|
|
172
|
+
let mut state = crate::state::projection::select_runtime_state(workspace, Some(team_key))
|
|
173
|
+
.map_err(|error| LifecycleError::TeamSelect(error.to_string()))?;
|
|
174
|
+
let matches = state
|
|
175
|
+
.get("agents")
|
|
176
|
+
.and_then(|agents| agents.get(agent_id.as_str()))
|
|
177
|
+
.and_then(|agent| agent.get("reservation_owner"))
|
|
178
|
+
.and_then(serde_json::Value::as_str)
|
|
179
|
+
== Some(owner);
|
|
180
|
+
if !matches {
|
|
181
|
+
return Ok(());
|
|
182
|
+
}
|
|
183
|
+
state
|
|
184
|
+
.get_mut("agents")
|
|
185
|
+
.and_then(serde_json::Value::as_object_mut)
|
|
186
|
+
.ok_or_else(|| {
|
|
187
|
+
LifecycleError::StatePersist("runtime state agents is not an object".into())
|
|
188
|
+
})?
|
|
189
|
+
.remove(agent_id.as_str());
|
|
190
|
+
save_launched_team_state_for_key(workspace, &state, Some(team_key), Some(agent_id.as_str()))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// ---
|
|
194
|
+
/// purpose: 加一席的默认入口,解析活跃 team、拿生命周期锁、路由到该 team 实际使用的 tmux socket
|
|
195
|
+
/// params:
|
|
196
|
+
/// role_file_path: 外部角色文档路径,就地读取不拷贝
|
|
197
|
+
/// open_display: 是否为新席开显示
|
|
198
|
+
/// returns: 新席的环境与启动模式
|
|
199
|
+
/// errors: 选不到 team 返回 TeamSelect,角色文件缺失或编译失败返回 Compile,重名返回 RequirementUnmet
|
|
200
|
+
/// contract_id: lifecycle.add_agent.entry
|
|
201
|
+
/// ---
|
|
17
202
|
/// `add_agent(workspace, agent_id, role_file_path, open_display, team)`
|
|
18
203
|
/// (`lifecycle/operations.py:143`)。动态 role doc 编译进 spec + 起 worker;失败**字节级回滚**
|
|
19
204
|
/// spec_yaml / workspace_state / **team_state.md** / role_file(Gap 15.11),每步发
|
|
@@ -32,18 +217,14 @@ pub fn add_agent(
|
|
|
32
217
|
) {
|
|
33
218
|
Ok(selected) => selected,
|
|
34
219
|
Err(_) if workspace.join("TEAM.md").exists() => {
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// and routes the new agent's spawn to the SAME tmux socket the live
|
|
39
|
-
// team uses. Cold workspaces / first-agent paths safely fall back to
|
|
40
|
-
// `TmuxBackend::for_workspace(team_workspace)` inside the resolver.
|
|
41
|
-
let team_ws = team_workspace(workspace);
|
|
220
|
+
// The MCP server already passes the canonical run workspace here.
|
|
221
|
+
// Reapplying team_workspace() would strip the scratch root and bind
|
|
222
|
+
// the first dynamic worker to the parent workspace's tmux server.
|
|
42
223
|
let transport =
|
|
43
224
|
crate::lifecycle::restart::lifecycle_worker_tmux_backend_for_selected_state(
|
|
44
|
-
|
|
225
|
+
workspace, team,
|
|
45
226
|
)
|
|
46
|
-
.unwrap_or_else(|_| crate::tmux_backend::TmuxBackend::for_workspace(
|
|
227
|
+
.unwrap_or_else(|_| crate::tmux_backend::TmuxBackend::for_workspace(workspace));
|
|
47
228
|
return add_agent_with_transport(
|
|
48
229
|
workspace,
|
|
49
230
|
agent_id,
|
|
@@ -55,12 +236,18 @@ pub fn add_agent(
|
|
|
55
236
|
}
|
|
56
237
|
Err(error) => return Err(LifecycleError::TeamSelect(error.to_string())),
|
|
57
238
|
};
|
|
58
|
-
let
|
|
239
|
+
let lifecycle_lock = acquire_agent_lifecycle_lock(LifecycleLockRequest {
|
|
59
240
|
workspace: &selected.run_workspace,
|
|
60
241
|
operation: "add-agent",
|
|
61
242
|
team: Some(selected.team_key.as_str()),
|
|
62
243
|
agent_id: Some(agent_id),
|
|
63
244
|
})?;
|
|
245
|
+
let reservation = reserve_agent_slot(
|
|
246
|
+
&selected.run_workspace,
|
|
247
|
+
Some(selected.team_key.as_str()),
|
|
248
|
+
agent_id,
|
|
249
|
+
)?;
|
|
250
|
+
drop(lifecycle_lock);
|
|
64
251
|
// E5 §3:compile_team 要角色定义目录(team_dir),不是 spec 落点(spec_workspace=runtime)。
|
|
65
252
|
let team_dir = selected.team_dir;
|
|
66
253
|
// **0.3.24 add-agent socket drift fix**: route to the live team's persisted
|
|
@@ -74,7 +261,7 @@ pub fn add_agent(
|
|
|
74
261
|
Some(selected.team_key.as_str()),
|
|
75
262
|
)
|
|
76
263
|
.unwrap_or_else(|_| crate::tmux_backend::TmuxBackend::for_workspace(&selected.run_workspace));
|
|
77
|
-
|
|
264
|
+
add_agent_with_transport_at_paths_reserved(
|
|
78
265
|
&selected.run_workspace,
|
|
79
266
|
&team_dir,
|
|
80
267
|
agent_id,
|
|
@@ -82,9 +269,19 @@ pub fn add_agent(
|
|
|
82
269
|
open_display,
|
|
83
270
|
Some(selected.team_key.as_str()),
|
|
84
271
|
&transport,
|
|
272
|
+
Some(reservation),
|
|
273
|
+
false,
|
|
85
274
|
)
|
|
86
275
|
}
|
|
87
276
|
|
|
277
|
+
/// ---
|
|
278
|
+
/// purpose: 强制重建一席,先快照旧席再摘除再加回
|
|
279
|
+
/// params:
|
|
280
|
+
/// force: 为假时直接退回普通 add_agent
|
|
281
|
+
/// returns: 新席的环境与启动模式
|
|
282
|
+
/// errors: 任一步失败都按快照恢复,恢复本身再出错时错误里附 rollback_errors
|
|
283
|
+
/// contract_id: lifecycle.add_agent.force_entry
|
|
284
|
+
/// ---
|
|
88
285
|
/// Reconcile a single existing/inconsistent seat, then reuse the normal add
|
|
89
286
|
/// path. The external role source is preserved by remove-agent ownership
|
|
90
287
|
/// checks, so this is a one-command force-recreate rather than a team restart.
|
|
@@ -128,6 +325,12 @@ pub fn add_agent_force(
|
|
|
128
325
|
)
|
|
129
326
|
}
|
|
130
327
|
|
|
328
|
+
/// ---
|
|
329
|
+
/// purpose: 带注入 transport 的加席入口,归一 workspace 并拿锁后转实体实现
|
|
330
|
+
/// returns: 新席的环境与启动模式
|
|
331
|
+
/// errors: 归一 workspace 失败返回 StatePersist,其余透传
|
|
332
|
+
/// contract_id: lifecycle.add_agent.entry
|
|
333
|
+
/// ---
|
|
131
334
|
/// `add_agent` with an injected transport — after the recompile+write, wires the new worker spawn
|
|
132
335
|
/// (via start_agent_with_transport) + start_coordinator (rt-host-a sweep: recompiled but never spawned).
|
|
133
336
|
pub(crate) fn add_agent_with_transport(
|
|
@@ -140,13 +343,15 @@ pub(crate) fn add_agent_with_transport(
|
|
|
140
343
|
) -> Result<AddAgentReport, LifecycleError> {
|
|
141
344
|
let run_workspace = crate::model::paths::canonical_run_workspace(workspace)
|
|
142
345
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
143
|
-
let
|
|
346
|
+
let lifecycle_lock = acquire_agent_lifecycle_lock(LifecycleLockRequest {
|
|
144
347
|
workspace: &run_workspace,
|
|
145
348
|
operation: "add-agent",
|
|
146
349
|
team,
|
|
147
350
|
agent_id: Some(agent_id),
|
|
148
351
|
})?;
|
|
149
|
-
|
|
352
|
+
let reservation = reserve_agent_slot(&run_workspace, team, agent_id)?;
|
|
353
|
+
drop(lifecycle_lock);
|
|
354
|
+
add_agent_with_transport_at_paths_reserved(
|
|
150
355
|
&run_workspace,
|
|
151
356
|
workspace,
|
|
152
357
|
agent_id,
|
|
@@ -154,9 +359,19 @@ pub(crate) fn add_agent_with_transport(
|
|
|
154
359
|
open_display,
|
|
155
360
|
team,
|
|
156
361
|
transport,
|
|
362
|
+
Some(reservation),
|
|
363
|
+
false,
|
|
157
364
|
)
|
|
158
365
|
}
|
|
159
366
|
|
|
367
|
+
/// ---
|
|
368
|
+
/// purpose: 带注入 transport 的强制重建入口
|
|
369
|
+
/// params:
|
|
370
|
+
/// force: 为假时退回普通 add_agent_with_transport
|
|
371
|
+
/// returns: 新席的环境与启动模式
|
|
372
|
+
/// errors: 归一 workspace 失败返回 StatePersist,其余透传
|
|
373
|
+
/// contract_id: lifecycle.add_agent.force_entry
|
|
374
|
+
/// ---
|
|
160
375
|
pub(crate) fn add_agent_with_transport_force(
|
|
161
376
|
workspace: &Path,
|
|
162
377
|
agent_id: &AgentId,
|
|
@@ -195,6 +410,11 @@ pub(crate) fn add_agent_with_transport_force(
|
|
|
195
410
|
)
|
|
196
411
|
}
|
|
197
412
|
|
|
413
|
+
/// ---
|
|
414
|
+
/// purpose: 已持锁状态下的强制重建,先校验替换源可用再消费旧席
|
|
415
|
+
/// returns: 新席报告;成功后还要过快照的一致性校验
|
|
416
|
+
/// errors: 角色文件不存在先行返回 Compile;摘除、加回或一致性校验失败时按快照恢复并返回错误
|
|
417
|
+
/// ---
|
|
198
418
|
pub(super) fn force_recreate_with_transport_locked(
|
|
199
419
|
run_workspace: &Path,
|
|
200
420
|
team_dir: &Path,
|
|
@@ -230,7 +450,7 @@ pub(super) fn force_recreate_with_transport_locked(
|
|
|
230
450
|
let restore_errors = snapshot.restore(team, transport);
|
|
231
451
|
return force_recreate_rollback_error(agent_id, error, restore_errors);
|
|
232
452
|
}
|
|
233
|
-
let operation =
|
|
453
|
+
let operation = add_agent_with_transport_at_paths_locked(
|
|
234
454
|
run_workspace,
|
|
235
455
|
team_dir,
|
|
236
456
|
agent_id,
|
|
@@ -256,6 +476,10 @@ pub(super) fn force_recreate_with_transport_locked(
|
|
|
256
476
|
}
|
|
257
477
|
}
|
|
258
478
|
|
|
479
|
+
/// ---
|
|
480
|
+
/// purpose: 把原始错误与回滚过程中的错误合成一个对外错误
|
|
481
|
+
/// returns: 回滚干净时原样返回原始错误;否则包成 StatePersist 并附 rollback_errors
|
|
482
|
+
/// ---
|
|
259
483
|
pub(super) fn force_recreate_rollback_error<T>(
|
|
260
484
|
agent_id: &AgentId,
|
|
261
485
|
error: LifecycleError,
|
|
@@ -271,6 +495,11 @@ pub(super) fn force_recreate_rollback_error<T>(
|
|
|
271
495
|
}
|
|
272
496
|
}
|
|
273
497
|
|
|
498
|
+
/// ---
|
|
499
|
+
/// purpose: 测试用注入点,按环境变量在 spawn 之后制造一次失败
|
|
500
|
+
/// returns: 环境变量未设或为空时直接成功
|
|
501
|
+
/// errors: 设了非空值时返回 StatePersist
|
|
502
|
+
/// ---
|
|
274
503
|
pub(super) fn maybe_fail_force_recreate_after_spawn() -> Result<(), LifecycleError> {
|
|
275
504
|
let Ok(reason) = std::env::var("TEAM_AGENT_TEST_FAIL_FORCE_RECREATE_AFTER_SPAWN") else {
|
|
276
505
|
return Ok(());
|
|
@@ -283,6 +512,36 @@ pub(super) fn maybe_fail_force_recreate_after_spawn() -> Result<(), LifecycleErr
|
|
|
283
512
|
)))
|
|
284
513
|
}
|
|
285
514
|
|
|
515
|
+
fn rollback_added_pi_wrapper(
|
|
516
|
+
run_workspace: &Path,
|
|
517
|
+
team_key: &str,
|
|
518
|
+
agent_id: &AgentId,
|
|
519
|
+
role_meta: &Value,
|
|
520
|
+
) -> Result<(), LifecycleError> {
|
|
521
|
+
if role_meta.get("provider").and_then(Value::as_str) != Some("pi") {
|
|
522
|
+
return Ok(());
|
|
523
|
+
}
|
|
524
|
+
let wrapper =
|
|
525
|
+
crate::lifecycle::launch::pi_mcp::pi_seat_paths(run_workspace, team_key, agent_id.as_str())
|
|
526
|
+
.wrapper;
|
|
527
|
+
match std::fs::remove_file(&wrapper) {
|
|
528
|
+
Ok(()) => Ok(()),
|
|
529
|
+
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
|
|
530
|
+
Err(error) => Err(LifecycleError::StatePersist(format!(
|
|
531
|
+
"failed to roll back added Pi wrapper {}: {error}",
|
|
532
|
+
wrapper.display()
|
|
533
|
+
))),
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/// ---
|
|
538
|
+
/// purpose: 加席的实体实现,含 owner 门、重名拒绝、重编译 spec 原子写、席位 state upsert 与起席
|
|
539
|
+
/// params:
|
|
540
|
+
/// run_workspace: 已归一的 run workspace
|
|
541
|
+
/// team_dir: 角色定义所在目录,编译 team 用它
|
|
542
|
+
/// returns: 新席的环境与启动模式
|
|
543
|
+
/// errors: owner 门不过或重名返回 RequirementUnmet,角色文件缺失或编译不一致返回 Compile,state 与 spawn 失败先回滚再透传原错
|
|
544
|
+
/// ---
|
|
286
545
|
pub(super) fn add_agent_with_transport_at_paths(
|
|
287
546
|
run_workspace: &Path,
|
|
288
547
|
team_dir: &Path,
|
|
@@ -291,6 +550,54 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
291
550
|
open_display: bool,
|
|
292
551
|
team: Option<&str>,
|
|
293
552
|
transport: &dyn Transport,
|
|
553
|
+
) -> Result<AddAgentReport, LifecycleError> {
|
|
554
|
+
add_agent_with_transport_at_paths_reserved(
|
|
555
|
+
run_workspace,
|
|
556
|
+
team_dir,
|
|
557
|
+
agent_id,
|
|
558
|
+
role_file_path,
|
|
559
|
+
open_display,
|
|
560
|
+
team,
|
|
561
|
+
transport,
|
|
562
|
+
None,
|
|
563
|
+
false,
|
|
564
|
+
)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
#[allow(clippy::too_many_arguments)]
|
|
568
|
+
fn add_agent_with_transport_at_paths_locked(
|
|
569
|
+
run_workspace: &Path,
|
|
570
|
+
team_dir: &Path,
|
|
571
|
+
agent_id: &AgentId,
|
|
572
|
+
role_file_path: &Path,
|
|
573
|
+
open_display: bool,
|
|
574
|
+
team: Option<&str>,
|
|
575
|
+
transport: &dyn Transport,
|
|
576
|
+
) -> Result<AddAgentReport, LifecycleError> {
|
|
577
|
+
add_agent_with_transport_at_paths_reserved(
|
|
578
|
+
run_workspace,
|
|
579
|
+
team_dir,
|
|
580
|
+
agent_id,
|
|
581
|
+
role_file_path,
|
|
582
|
+
open_display,
|
|
583
|
+
team,
|
|
584
|
+
transport,
|
|
585
|
+
None,
|
|
586
|
+
true,
|
|
587
|
+
)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
#[allow(clippy::too_many_arguments)]
|
|
591
|
+
fn add_agent_with_transport_at_paths_reserved(
|
|
592
|
+
run_workspace: &Path,
|
|
593
|
+
team_dir: &Path,
|
|
594
|
+
agent_id: &AgentId,
|
|
595
|
+
role_file_path: &Path,
|
|
596
|
+
open_display: bool,
|
|
597
|
+
team: Option<&str>,
|
|
598
|
+
transport: &dyn Transport,
|
|
599
|
+
reservation: Option<AgentReservation>,
|
|
600
|
+
lifecycle_lock_held: bool,
|
|
294
601
|
) -> Result<AddAgentReport, LifecycleError> {
|
|
295
602
|
let runtime_state = crate::state::persist::load_runtime_state(run_workspace)
|
|
296
603
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
@@ -309,7 +616,15 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
309
616
|
role_file_path.display()
|
|
310
617
|
)));
|
|
311
618
|
}
|
|
312
|
-
|
|
619
|
+
let reservation_owned = reservation.as_ref().is_some_and(|reservation| {
|
|
620
|
+
owner_state
|
|
621
|
+
.get("agents")
|
|
622
|
+
.and_then(|agents| agents.get(agent_id.as_str()))
|
|
623
|
+
.and_then(|agent| agent.get("reservation_owner"))
|
|
624
|
+
.and_then(serde_json::Value::as_str)
|
|
625
|
+
== Some(reservation.owner.as_str())
|
|
626
|
+
});
|
|
627
|
+
if runtime_agent_exists(&owner_state, agent_id) && !reservation_owned {
|
|
313
628
|
return Err(LifecycleError::RequirementUnmet(format!(
|
|
314
629
|
"agent id already exists: {agent_id}"
|
|
315
630
|
)));
|
|
@@ -336,20 +651,40 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
336
651
|
compiled.id, agent_id
|
|
337
652
|
)));
|
|
338
653
|
}
|
|
339
|
-
|
|
654
|
+
let compiled_agent = compiled.agent.clone();
|
|
655
|
+
inject_agent_into_spec(&mut spec, compiled_agent.clone(), &compiled.id)?;
|
|
340
656
|
// E5 spec 迁移:重编译的 spec 原子写到 .team/runtime/<team_key>/(不落用户目录 team_dir)。
|
|
341
657
|
let spec_path = crate::model::paths::runtime_spec_path(run_workspace, &canonical_team_key);
|
|
342
658
|
// E42 (0.3.24 P0): capture pre-write bytes for atomic rollback. If anything
|
|
343
659
|
// downstream of write_spec_atomic + upsert_agent_state_from_role + spawn
|
|
344
660
|
// fails, restore the prior bytes so the canonical spec / runtime state never
|
|
345
661
|
// get a half-written row that disagrees with what remove-agent can see.
|
|
662
|
+
let reservation_lock = if lifecycle_lock_held {
|
|
663
|
+
None
|
|
664
|
+
} else {
|
|
665
|
+
Some(acquire_agent_lifecycle_lock(LifecycleLockRequest {
|
|
666
|
+
workspace: run_workspace,
|
|
667
|
+
operation: "add-agent",
|
|
668
|
+
team,
|
|
669
|
+
agent_id: Some(agent_id),
|
|
670
|
+
})?)
|
|
671
|
+
};
|
|
346
672
|
let pre_spec_text = match std::fs::read_to_string(&spec_path) {
|
|
347
673
|
Ok(text) => Some(text),
|
|
348
674
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
|
|
349
675
|
Err(e) => return Err(LifecycleError::StatePersist(format!("read spec: {e}"))),
|
|
350
676
|
};
|
|
351
|
-
|
|
352
|
-
|
|
677
|
+
// Merge against the latest runtime spec while holding only the short
|
|
678
|
+
// filesystem critical section. Role compilation happened before this lock;
|
|
679
|
+
// peer reservations therefore remain visible instead of being overwritten
|
|
680
|
+
// by a stale base-spec snapshot.
|
|
681
|
+
let mut latest_spec = pre_spec_text
|
|
682
|
+
.as_deref()
|
|
683
|
+
.and_then(|text| crate::model::yaml::loads(text).ok())
|
|
684
|
+
.unwrap_or(spec);
|
|
685
|
+
inject_agent_into_spec(&mut latest_spec, compiled_agent, &compiled.id)?;
|
|
686
|
+
write_spec_atomic(&spec_path, &latest_spec)?;
|
|
687
|
+
drop(reservation_lock);
|
|
353
688
|
let (meta, _) = crate::compiler::read_front_matter(role_file_path)
|
|
354
689
|
.map_err(|e| LifecycleError::Compile(e.to_string()))?;
|
|
355
690
|
// upsert writes status="starting" (E42) — start_agent_at_paths::mark_agent_started
|
|
@@ -366,7 +701,7 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
366
701
|
run_workspace,
|
|
367
702
|
&spec_path,
|
|
368
703
|
pre_spec_text.as_deref(),
|
|
369
|
-
|
|
704
|
+
None,
|
|
370
705
|
agent_id,
|
|
371
706
|
"state_upsert_failed",
|
|
372
707
|
);
|
|
@@ -384,36 +719,73 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
384
719
|
) {
|
|
385
720
|
Ok(started) => started,
|
|
386
721
|
Err(error) => {
|
|
722
|
+
let wrapper_rollback =
|
|
723
|
+
rollback_added_pi_wrapper(run_workspace, &canonical_team_key, agent_id, &meta);
|
|
387
724
|
rollback_add_agent_atomic(
|
|
388
725
|
run_workspace,
|
|
389
726
|
&spec_path,
|
|
390
727
|
pre_spec_text.as_deref(),
|
|
391
|
-
|
|
728
|
+
None,
|
|
392
729
|
agent_id,
|
|
393
730
|
"start_agent_failed",
|
|
394
731
|
);
|
|
395
|
-
return
|
|
732
|
+
return match wrapper_rollback {
|
|
733
|
+
Ok(()) => Err(error),
|
|
734
|
+
Err(rollback_error) => Err(LifecycleError::StatePersist(format!(
|
|
735
|
+
"{error}; {rollback_error}"
|
|
736
|
+
))),
|
|
737
|
+
};
|
|
396
738
|
}
|
|
397
739
|
};
|
|
398
740
|
let (env, start_mode) = match started {
|
|
399
741
|
StartAgentOutcome::Running {
|
|
400
742
|
env, start_mode, ..
|
|
401
743
|
} => (env, start_mode),
|
|
402
|
-
StartAgentOutcome::Noop {
|
|
744
|
+
StartAgentOutcome::Noop { .. } => {
|
|
745
|
+
let wrapper_rollback =
|
|
746
|
+
rollback_added_pi_wrapper(run_workspace, &canonical_team_key, agent_id, &meta);
|
|
747
|
+
rollback_add_agent_atomic(
|
|
748
|
+
run_workspace,
|
|
749
|
+
&spec_path,
|
|
750
|
+
pre_spec_text.as_deref(),
|
|
751
|
+
None,
|
|
752
|
+
agent_id,
|
|
753
|
+
"added_agent_noop",
|
|
754
|
+
);
|
|
755
|
+
let error = LifecycleError::RequirementUnmet(format!(
|
|
756
|
+
"newly added agent {agent_id} returned start_agent.noop"
|
|
757
|
+
));
|
|
758
|
+
return match wrapper_rollback {
|
|
759
|
+
Ok(()) => Err(error),
|
|
760
|
+
Err(rollback_error) => Err(LifecycleError::StatePersist(format!(
|
|
761
|
+
"{error}; {rollback_error}"
|
|
762
|
+
))),
|
|
763
|
+
};
|
|
764
|
+
}
|
|
403
765
|
StartAgentOutcome::Paused { .. } => {
|
|
766
|
+
let wrapper_rollback =
|
|
767
|
+
rollback_added_pi_wrapper(run_workspace, &canonical_team_key, agent_id, &meta);
|
|
404
768
|
rollback_add_agent_atomic(
|
|
405
769
|
run_workspace,
|
|
406
770
|
&spec_path,
|
|
407
771
|
pre_spec_text.as_deref(),
|
|
408
|
-
|
|
772
|
+
None,
|
|
409
773
|
agent_id,
|
|
410
774
|
"added_agent_paused",
|
|
411
775
|
);
|
|
412
|
-
|
|
413
|
-
"added agent {agent_id} is paused"
|
|
414
|
-
|
|
776
|
+
let error =
|
|
777
|
+
LifecycleError::RequirementUnmet(format!("added agent {agent_id} is paused"));
|
|
778
|
+
return match wrapper_rollback {
|
|
779
|
+
Ok(()) => Err(error),
|
|
780
|
+
Err(rollback_error) => Err(LifecycleError::StatePersist(format!(
|
|
781
|
+
"{error}; {rollback_error}"
|
|
782
|
+
))),
|
|
783
|
+
};
|
|
415
784
|
}
|
|
416
785
|
};
|
|
786
|
+
if let Some(reservation) = reservation {
|
|
787
|
+
reservation.commit();
|
|
788
|
+
}
|
|
417
789
|
Ok(AddAgentReport {
|
|
418
790
|
env,
|
|
419
791
|
start_mode,
|