@team-agent/installer 0.3.1 → 0.3.2
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 +38 -7
- package/crates/team-agent/src/cli/emit.rs +7 -6
- package/crates/team-agent/src/cli/mod.rs +623 -21
- package/crates/team-agent/src/cli/status_port.rs +170 -44
- package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -0
- package/crates/team-agent/src/cli/types.rs +1 -0
- package/crates/team-agent/src/coordinator/health.rs +9 -0
- package/crates/team-agent/src/lifecycle/launch.rs +271 -58
- package/crates/team-agent/src/lifecycle/restart/common.rs +65 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +57 -15
- package/crates/team-agent/src/lifecycle/restart/remove.rs +5 -1
- package/crates/team-agent/src/lifecycle/restart.rs +20 -0
- package/crates/team-agent/src/messaging/delivery.rs +397 -36
- package/crates/team-agent/src/messaging/mod.rs +1 -1
- package/crates/team-agent/src/messaging/results.rs +200 -47
- package/crates/team-agent/src/provider/adapter.rs +95 -10
- package/crates/team-agent/src/provider/helpers.rs +10 -1
- package/crates/team-agent/src/state/persist.rs +113 -1
- package/crates/team-agent/src/state/projection.rs +127 -3
- package/crates/team-agent/src/tmux_backend.rs +66 -6
- package/package.json +4 -4
|
@@ -31,6 +31,12 @@ pub fn restart_with_transport(
|
|
|
31
31
|
team: Option<&str>,
|
|
32
32
|
transport: &dyn crate::transport::Transport,
|
|
33
33
|
) -> Result<RestartReport, LifecycleError> {
|
|
34
|
+
if crate::lifecycle::restart::input_has_no_local_team_context(workspace) {
|
|
35
|
+
return Err(LifecycleError::TeamSelect(format!(
|
|
36
|
+
"missing spec for restart: {}",
|
|
37
|
+
workspace.join("team.spec.yaml").display()
|
|
38
|
+
)));
|
|
39
|
+
}
|
|
34
40
|
let run_candidate = crate::model::paths::canonical_run_workspace(workspace)
|
|
35
41
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
36
42
|
if !workspace.join("team.spec.yaml").exists()
|
|
@@ -47,7 +53,7 @@ pub fn restart_with_transport(
|
|
|
47
53
|
crate::state::selector::SelectorMode::RequireSpec,
|
|
48
54
|
)
|
|
49
55
|
.map_err(|e| LifecycleError::TeamSelect(e.to_string()))?;
|
|
50
|
-
let state = selected.state;
|
|
56
|
+
let mut state = selected.state;
|
|
51
57
|
crate::lifecycle::launch::ensure_owner_allowed_for_state(&state, None)?;
|
|
52
58
|
let spec_workspace = selected
|
|
53
59
|
.spec_workspace
|
|
@@ -55,6 +61,10 @@ pub fn restart_with_transport(
|
|
|
55
61
|
.ok_or_else(|| LifecycleError::TeamSelect("active team spec workspace not found".to_string()))?;
|
|
56
62
|
let spec = load_team_spec(spec_workspace)?;
|
|
57
63
|
let safety = crate::lifecycle::launch::effective_runtime_config(&spec)?;
|
|
64
|
+
if refresh_missing_provider_sessions(&mut state)? {
|
|
65
|
+
crate::state::projection::save_team_scoped_state(&selected.run_workspace, &state)
|
|
66
|
+
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
67
|
+
}
|
|
58
68
|
let plan = classify_restart_plan(&state, allow_fresh)?;
|
|
59
69
|
write_restart_resume_decision_events(&selected.run_workspace, &state, allow_fresh, &plan.decisions)?;
|
|
60
70
|
if !plan.corrupt_entries.is_empty() {
|
|
@@ -117,7 +127,6 @@ fn write_restart_resume_decision_events(
|
|
|
117
127
|
allow_fresh: bool,
|
|
118
128
|
decisions: &[RestartedAgent],
|
|
119
129
|
) -> Result<(), LifecycleError> {
|
|
120
|
-
let log = crate::event_log::EventLog::new(workspace);
|
|
121
130
|
for decision in decisions {
|
|
122
131
|
let agent = state
|
|
123
132
|
.get("agents")
|
|
@@ -134,23 +143,56 @@ fn write_restart_resume_decision_events(
|
|
|
134
143
|
ResumeDecision::FreshStart => "fresh_start",
|
|
135
144
|
ResumeDecision::Refuse => "refuse",
|
|
136
145
|
};
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
"first_send_at": first_send_at,
|
|
146
|
-
"session_id": session_id,
|
|
147
|
-
}),
|
|
148
|
-
)
|
|
149
|
-
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
146
|
+
write_restart_resume_decision_event(
|
|
147
|
+
workspace,
|
|
148
|
+
decision.agent_id.as_str(),
|
|
149
|
+
first_send_at,
|
|
150
|
+
session_id,
|
|
151
|
+
allow_fresh,
|
|
152
|
+
decision_wire,
|
|
153
|
+
)?;
|
|
150
154
|
}
|
|
151
155
|
Ok(())
|
|
152
156
|
}
|
|
153
157
|
|
|
158
|
+
fn write_restart_resume_decision_event(
|
|
159
|
+
workspace: &Path,
|
|
160
|
+
worker_id: &str,
|
|
161
|
+
first_send_at: Option<String>,
|
|
162
|
+
session_id: Option<String>,
|
|
163
|
+
allow_fresh: bool,
|
|
164
|
+
decision: &str,
|
|
165
|
+
) -> Result<(), LifecycleError> {
|
|
166
|
+
use std::io::Write as _;
|
|
167
|
+
|
|
168
|
+
let path = workspace.join(".team").join("logs").join("events.jsonl");
|
|
169
|
+
if let Some(parent) = path.parent() {
|
|
170
|
+
std::fs::create_dir_all(parent)
|
|
171
|
+
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
172
|
+
}
|
|
173
|
+
let event = serde_json::json!({
|
|
174
|
+
"ts": chrono::Utc::now().to_rfc3339(),
|
|
175
|
+
"event": crate::lifecycle::types::event_names::RESTART_RESUME_DECISION,
|
|
176
|
+
"worker_id": worker_id,
|
|
177
|
+
"has_first_send_at": first_send_at.is_some(),
|
|
178
|
+
"has_session_id": session_id.is_some(),
|
|
179
|
+
"allow_fresh": allow_fresh,
|
|
180
|
+
"decision": decision,
|
|
181
|
+
"first_send_at": first_send_at,
|
|
182
|
+
"session_id": session_id,
|
|
183
|
+
});
|
|
184
|
+
let line = serde_json::to_string(&event)
|
|
185
|
+
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
186
|
+
let mut file = std::fs::OpenOptions::new()
|
|
187
|
+
.create(true)
|
|
188
|
+
.append(true)
|
|
189
|
+
.open(&path)
|
|
190
|
+
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
191
|
+
file.write_all(line.as_bytes())
|
|
192
|
+
.and_then(|_| file.write_all(b"\n"))
|
|
193
|
+
.map_err(|e| LifecycleError::StatePersist(e.to_string()))
|
|
194
|
+
}
|
|
195
|
+
|
|
154
196
|
/// `restart_candidates(workspace)`(`restart/selection.py:12`)。从 snapshot + active
|
|
155
197
|
/// state 收集可重启 team。
|
|
156
198
|
pub fn restart_candidates(workspace: &Path) -> Result<Vec<RestartCandidate>, LifecycleError> {
|
|
@@ -169,7 +169,11 @@ fn remove_agent_inner(
|
|
|
169
169
|
// (team projection) — NOT a raw save, so other teams in a multi-team workspace are preserved.
|
|
170
170
|
let mut removed_state = working_state;
|
|
171
171
|
remove_agent_from_state(&mut removed_state, agent_id)?;
|
|
172
|
-
crate::state::projection::
|
|
172
|
+
crate::state::projection::save_team_scoped_state_with_deleted_agents(
|
|
173
|
+
paths.run_workspace,
|
|
174
|
+
&removed_state,
|
|
175
|
+
&[agent_id.as_str()],
|
|
176
|
+
)
|
|
173
177
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
174
178
|
cleared_locations.push(serde_json::json!("state.json:agents"));
|
|
175
179
|
write_remove_step_event(
|
|
@@ -33,6 +33,7 @@ mod team_state;
|
|
|
33
33
|
|
|
34
34
|
pub use agent::{reset_agent, reset_agent_with_transport, start_agent, start_agent_with_transport, stop_agent, stop_agent_with_transport};
|
|
35
35
|
pub(crate) use agent::start_agent_at_paths;
|
|
36
|
+
pub(crate) use common::refresh_missing_provider_sessions;
|
|
36
37
|
pub use orchestrator::{halt_plan, plan_status};
|
|
37
38
|
pub use rebuild::{restart, restart_candidates, restart_with_transport, select_restart_state};
|
|
38
39
|
pub use remove::{remove_agent, remove_agent_with_transport};
|
|
@@ -45,6 +46,13 @@ pub(crate) fn lifecycle_run_workspace(workspace: &Path) -> Result<std::path::Pat
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
fn lifecycle_paths(workspace: &Path, team: Option<&str>) -> Result<LifecyclePaths, LifecycleError> {
|
|
49
|
+
if input_has_no_local_team_context(workspace) {
|
|
50
|
+
return Err(LifecycleError::TeamSelect(format!(
|
|
51
|
+
"active team spec not found: input_workspace={} expected_spec_path={}",
|
|
52
|
+
workspace.display(),
|
|
53
|
+
workspace.join("team.spec.yaml").display()
|
|
54
|
+
)));
|
|
55
|
+
}
|
|
48
56
|
let selected = crate::state::selector::resolve_active_team(
|
|
49
57
|
workspace,
|
|
50
58
|
team,
|
|
@@ -60,6 +68,18 @@ fn lifecycle_paths(workspace: &Path, team: Option<&str>) -> Result<LifecyclePath
|
|
|
60
68
|
})
|
|
61
69
|
}
|
|
62
70
|
|
|
71
|
+
pub(crate) fn input_has_no_local_team_context(workspace: &Path) -> bool {
|
|
72
|
+
!workspace.join("team.spec.yaml").exists()
|
|
73
|
+
&& !workspace.join(".team").exists()
|
|
74
|
+
&& !crate::state::persist::runtime_state_path(workspace).exists()
|
|
75
|
+
&& workspace.file_name().and_then(|s| s.to_str()) != Some(".team")
|
|
76
|
+
&& workspace
|
|
77
|
+
.parent()
|
|
78
|
+
.and_then(|p| p.file_name())
|
|
79
|
+
.and_then(|s| s.to_str())
|
|
80
|
+
!= Some(".team")
|
|
81
|
+
}
|
|
82
|
+
|
|
63
83
|
fn selected_state_spec_workspace(state: &serde_json::Value) -> Option<std::path::PathBuf> {
|
|
64
84
|
state
|
|
65
85
|
.get("spec_path")
|