@team-agent/installer 0.3.13 → 0.3.15
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 +1 -0
- package/crates/team-agent/src/cli/emit.rs +92 -11
- package/crates/team-agent/src/cli/mod.rs +228 -85
- package/crates/team-agent/src/cli/send.rs +252 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +15 -3
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +159 -2
- package/crates/team-agent/src/cli/types.rs +30 -1
- package/crates/team-agent/src/compiler/tests.rs +2 -2
- package/crates/team-agent/src/compiler.rs +1 -1
- package/crates/team-agent/src/fake_worker.rs +32 -145
- package/crates/team-agent/src/leader/start.rs +279 -4
- package/crates/team-agent/src/lifecycle/display.rs +3 -3
- package/crates/team-agent/src/lifecycle/launch.rs +692 -92
- package/crates/team-agent/src/lifecycle/restart/agent.rs +137 -17
- package/crates/team-agent/src/lifecycle/restart/common.rs +88 -42
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +197 -33
- package/crates/team-agent/src/lifecycle/restart/selection.rs +9 -6
- package/crates/team-agent/src/lifecycle/tests/core.rs +195 -50
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +934 -72
- package/crates/team-agent/src/lifecycle/types.rs +5 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +8 -0
- package/crates/team-agent/src/mcp_server/wire.rs +153 -3
- package/crates/team-agent/src/messaging/leader_receiver.rs +276 -43
- package/crates/team-agent/src/messaging/mod.rs +6 -3
- package/crates/team-agent/src/messaging/results.rs +240 -158
- package/crates/team-agent/src/messaging/send.rs +3 -2
- package/crates/team-agent/src/messaging/tests/e23.rs +35 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +6 -2
- package/crates/team-agent/src/messaging/tests/runtime.rs +9 -9
- package/crates/team-agent/src/os_probe.rs +11 -0
- package/crates/team-agent/src/state/persist.rs +6 -0
- package/crates/team-agent/src/tmux_backend.rs +90 -0
- package/crates/team-agent/src/transport/test_support.rs +46 -1
- package/crates/team-agent/src/transport.rs +31 -0
- package/package.json +4 -4
- package/skills/team-agent/references/recovery-runbook.md +277 -0
|
@@ -86,7 +86,14 @@ pub(crate) fn start_agent_at_paths(
|
|
|
86
86
|
}
|
|
87
87
|
let session_name = state_session_name(&state);
|
|
88
88
|
let window = agent_window(&agent, agent_id);
|
|
89
|
-
|
|
89
|
+
let adaptive_layout =
|
|
90
|
+
open_display && crate::lifecycle::launch::state_uses_adaptive_layout(&state);
|
|
91
|
+
let agent_live = if adaptive_layout {
|
|
92
|
+
agent_pane_live(transport, &agent)
|
|
93
|
+
} else {
|
|
94
|
+
window_exists(transport, &session_name, &window)
|
|
95
|
+
};
|
|
96
|
+
if !force && agent_live {
|
|
90
97
|
mark_agent_running_noop(&mut state, agent_id, &session_name, &window)?;
|
|
91
98
|
crate::state::projection::save_team_scoped_state(workspace, &state)
|
|
92
99
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
@@ -141,6 +148,28 @@ pub(crate) fn start_agent_at_paths(
|
|
|
141
148
|
let into_existing_session =
|
|
142
149
|
session_live_or_default(transport, &session_name, session_name_present(&state));
|
|
143
150
|
let safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn()?;
|
|
151
|
+
let layout_placement = if adaptive_layout {
|
|
152
|
+
crate::lifecycle::launch::adaptive_existing_placement_for_agent(
|
|
153
|
+
&state,
|
|
154
|
+
transport,
|
|
155
|
+
&session_name,
|
|
156
|
+
agent_id,
|
|
157
|
+
)
|
|
158
|
+
.or_else(|| {
|
|
159
|
+
crate::lifecycle::launch::adaptive_placement_for_agent(
|
|
160
|
+
&state,
|
|
161
|
+
transport,
|
|
162
|
+
&session_name,
|
|
163
|
+
agent_id,
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
} else {
|
|
167
|
+
None
|
|
168
|
+
};
|
|
169
|
+
let spawn_window = layout_placement
|
|
170
|
+
.as_ref()
|
|
171
|
+
.map(|placement| placement.layout_window.as_str().to_string())
|
|
172
|
+
.unwrap_or_else(|| window.clone());
|
|
144
173
|
let spawn = spawn_agent_window(
|
|
145
174
|
workspace,
|
|
146
175
|
&session_name,
|
|
@@ -150,9 +179,10 @@ pub(crate) fn start_agent_at_paths(
|
|
|
150
179
|
into_existing_session,
|
|
151
180
|
transport,
|
|
152
181
|
Some(&safety),
|
|
182
|
+
layout_placement.as_ref(),
|
|
153
183
|
None,
|
|
154
184
|
)?;
|
|
155
|
-
mark_agent_started(&mut state, agent_id, &
|
|
185
|
+
mark_agent_started(&mut state, agent_id, &spawn_window, &spawn, &safety)?;
|
|
156
186
|
crate::state::projection::save_team_scoped_state(workspace, &state)
|
|
157
187
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
158
188
|
write_start_agent_start_event(
|
|
@@ -162,9 +192,9 @@ pub(crate) fn start_agent_at_paths(
|
|
|
162
192
|
provider,
|
|
163
193
|
start_mode,
|
|
164
194
|
&session_name,
|
|
165
|
-
&
|
|
195
|
+
&spawn_window,
|
|
166
196
|
spawn_session_id,
|
|
167
|
-
into_existing_session,
|
|
197
|
+
tmux_start_mode_for_spawn(&spawn, into_existing_session),
|
|
168
198
|
)?;
|
|
169
199
|
let coordinator_started = start_coordinator_for_workspace(workspace)?;
|
|
170
200
|
Ok(StartAgentOutcome::Running {
|
|
@@ -180,6 +210,55 @@ pub(crate) fn start_agent_at_paths(
|
|
|
180
210
|
})
|
|
181
211
|
}
|
|
182
212
|
|
|
213
|
+
fn agent_pane_live(
|
|
214
|
+
transport: &dyn crate::transport::Transport,
|
|
215
|
+
agent: &serde_json::Value,
|
|
216
|
+
) -> bool {
|
|
217
|
+
let Some(pane) = agent
|
|
218
|
+
.get("pane_id")
|
|
219
|
+
.and_then(serde_json::Value::as_str)
|
|
220
|
+
.filter(|pane| !pane.is_empty())
|
|
221
|
+
.map(crate::transport::PaneId::new)
|
|
222
|
+
else {
|
|
223
|
+
return false;
|
|
224
|
+
};
|
|
225
|
+
agent_pane_live_by_id(transport, &pane)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fn agent_pane_live_by_id(
|
|
229
|
+
transport: &dyn crate::transport::Transport,
|
|
230
|
+
pane: &crate::transport::PaneId,
|
|
231
|
+
) -> bool {
|
|
232
|
+
match transport.has_pane(&pane) {
|
|
233
|
+
Ok(Some(live)) => live,
|
|
234
|
+
Ok(None) | Err(_) => !matches!(
|
|
235
|
+
transport.liveness(&pane),
|
|
236
|
+
Ok(crate::model::enums::PaneLiveness::Dead)
|
|
237
|
+
),
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
fn tmux_start_mode_for_spawn(
|
|
242
|
+
spawn: &SpawnedAgentWindow,
|
|
243
|
+
into_existing_session: bool,
|
|
244
|
+
) -> &'static str {
|
|
245
|
+
if let Some(placement) = spawn.layout_placement.as_ref() {
|
|
246
|
+
if placement.starts_window {
|
|
247
|
+
if into_existing_session {
|
|
248
|
+
"new-window"
|
|
249
|
+
} else {
|
|
250
|
+
"new-session"
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
"split-window"
|
|
254
|
+
}
|
|
255
|
+
} else if into_existing_session {
|
|
256
|
+
"new-window"
|
|
257
|
+
} else {
|
|
258
|
+
"new-session"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
183
262
|
/// `stop_agent(workspace, agent_id, team)`(`lifecycle/operations.py:62`)。
|
|
184
263
|
/// owner-gate → kill window → **同时关显示** → 写 state。
|
|
185
264
|
pub fn stop_agent(
|
|
@@ -233,16 +312,34 @@ pub(super) fn stop_agent_at_paths(
|
|
|
233
312
|
.filter(|s| !s.is_empty())
|
|
234
313
|
.unwrap_or_else(|| agent_id.as_str())
|
|
235
314
|
.to_string();
|
|
236
|
-
let
|
|
237
|
-
|
|
315
|
+
let pane_id = state
|
|
316
|
+
.get("agents")
|
|
317
|
+
.and_then(|v| v.get(agent_id.as_str()))
|
|
318
|
+
.and_then(|v| v.get("pane_id"))
|
|
319
|
+
.and_then(|v| v.as_str())
|
|
320
|
+
.filter(|pane| !pane.is_empty())
|
|
321
|
+
.map(crate::transport::PaneId::new);
|
|
322
|
+
let target_str = pane_id
|
|
323
|
+
.as_ref()
|
|
324
|
+
.map(|pane| pane.as_str().to_string())
|
|
325
|
+
.unwrap_or_else(|| format!("{}:{window}", session_name.as_str()));
|
|
326
|
+
let stopped = pane_id
|
|
327
|
+
.as_ref()
|
|
328
|
+
.map(|pane| agent_pane_live_by_id(transport, pane))
|
|
329
|
+
.unwrap_or_else(|| window_exists(transport, &session_name, &window));
|
|
238
330
|
if stopped {
|
|
239
|
-
let target = Target::SessionWindow {
|
|
240
|
-
session: session_name.clone(),
|
|
241
|
-
window: WindowName::new(&window),
|
|
242
|
-
};
|
|
243
331
|
// golden operations.py:84-86: a non-zero kill-window raises
|
|
244
332
|
// RuntimeError(f"failed to stop agent {agent_id}: {proc.stderr.strip()}").
|
|
245
|
-
if let
|
|
333
|
+
let kill_result = if let Some(pane) = pane_id.as_ref() {
|
|
334
|
+
transport.kill_pane(pane)
|
|
335
|
+
} else {
|
|
336
|
+
let target = Target::SessionWindow {
|
|
337
|
+
session: session_name.clone(),
|
|
338
|
+
window: WindowName::new(&window),
|
|
339
|
+
};
|
|
340
|
+
transport.kill_window(&target)
|
|
341
|
+
};
|
|
342
|
+
if let Err(e) = kill_result {
|
|
246
343
|
let stderr = match &e {
|
|
247
344
|
crate::transport::TransportError::Subprocess { stderr, .. } => stderr.trim().to_string(),
|
|
248
345
|
other => other.to_string(),
|
|
@@ -335,6 +432,34 @@ fn mark_agent_started(
|
|
|
335
432
|
&spawn.profile_launch,
|
|
336
433
|
);
|
|
337
434
|
crate::lifecycle::launch::persist_effective_approval_policy(agent, safety);
|
|
435
|
+
if let Some(placement) = spawn.layout_placement.as_ref() {
|
|
436
|
+
agent.insert(
|
|
437
|
+
"layout_window".to_string(),
|
|
438
|
+
serde_json::json!(placement.layout_window.as_str()),
|
|
439
|
+
);
|
|
440
|
+
agent.insert(
|
|
441
|
+
"layout_index".to_string(),
|
|
442
|
+
serde_json::json!(placement.layout_index),
|
|
443
|
+
);
|
|
444
|
+
agent.insert("pane_index".to_string(), serde_json::json!(placement.pane_index));
|
|
445
|
+
agent.insert(
|
|
446
|
+
"display".to_string(),
|
|
447
|
+
serde_json::json!({
|
|
448
|
+
"backend": "adaptive",
|
|
449
|
+
"status": "opened",
|
|
450
|
+
"window": placement.layout_window.as_str(),
|
|
451
|
+
"workspace_window": null,
|
|
452
|
+
"pane_id": spawn.spawn.pane_id.as_str(),
|
|
453
|
+
"pane_title": agent_id.as_str(),
|
|
454
|
+
"target": spawn.spawn.pane_id.as_str(),
|
|
455
|
+
"target_worker_session": spawn.spawn.session.as_str(),
|
|
456
|
+
"linked_session": null,
|
|
457
|
+
"leader_session": spawn.spawn.session.as_str(),
|
|
458
|
+
"display_session": null,
|
|
459
|
+
"fallback": null,
|
|
460
|
+
}),
|
|
461
|
+
);
|
|
462
|
+
}
|
|
338
463
|
Ok(())
|
|
339
464
|
}
|
|
340
465
|
|
|
@@ -458,7 +583,7 @@ fn write_start_agent_start_event(
|
|
|
458
583
|
session_name: &SessionName,
|
|
459
584
|
window: &str,
|
|
460
585
|
session_id: Option<&SessionId>,
|
|
461
|
-
|
|
586
|
+
tmux_start_mode: &'static str,
|
|
462
587
|
) -> Result<(), LifecycleError> {
|
|
463
588
|
let auth_mode = agent_auth_mode(agent);
|
|
464
589
|
let model = agent.get("model").and_then(|v| v.as_str());
|
|
@@ -535,11 +660,6 @@ fn write_start_agent_start_event(
|
|
|
535
660
|
agent_id.as_str(),
|
|
536
661
|
team_id,
|
|
537
662
|
);
|
|
538
|
-
let tmux_start_mode = if into_existing_session {
|
|
539
|
-
"new-window"
|
|
540
|
-
} else {
|
|
541
|
-
"new-session"
|
|
542
|
-
};
|
|
543
663
|
crate::event_log::EventLog::new(workspace)
|
|
544
664
|
.write(
|
|
545
665
|
"start_agent.agent_start",
|
|
@@ -4,6 +4,7 @@ pub(super) struct SpawnedAgentWindow {
|
|
|
4
4
|
pub spawn: crate::transport::SpawnResult,
|
|
5
5
|
pub plan: crate::provider::CommandPlan,
|
|
6
6
|
pub profile_launch: crate::provider::ProviderProfileLaunch,
|
|
7
|
+
pub layout_placement: Option<crate::lifecycle::launch::LayoutPlacement>,
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
pub(super) fn spawn_agent_window(
|
|
@@ -15,6 +16,7 @@ pub(super) fn spawn_agent_window(
|
|
|
15
16
|
into_existing_session: bool,
|
|
16
17
|
transport: &dyn crate::transport::Transport,
|
|
17
18
|
safety: Option<&DangerousApproval>,
|
|
19
|
+
layout_placement: Option<&crate::lifecycle::launch::LayoutPlacement>,
|
|
18
20
|
spawn_cwd_override: Option<&Path>,
|
|
19
21
|
) -> Result<SpawnedAgentWindow, LifecycleError> {
|
|
20
22
|
let provider = agent_provider(agent);
|
|
@@ -36,12 +38,11 @@ pub(super) fn spawn_agent_window(
|
|
|
36
38
|
detected_safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn()?;
|
|
37
39
|
&detected_safety
|
|
38
40
|
};
|
|
39
|
-
let command_agent =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
41
|
+
let command_agent = crate::lifecycle::worker_command_context::WorkerCommandAgent::from_json(
|
|
42
|
+
agent,
|
|
43
|
+
Some(agent_id.as_str()),
|
|
44
|
+
provider,
|
|
45
|
+
);
|
|
45
46
|
let system_prompt =
|
|
46
47
|
crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
|
|
47
48
|
let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
|
|
@@ -60,7 +61,8 @@ pub(super) fn spawn_agent_window(
|
|
|
60
61
|
.and_then(|v| v.as_str())
|
|
61
62
|
.map(str::to_string)
|
|
62
63
|
.or_else(|| {
|
|
63
|
-
let key =
|
|
64
|
+
let key =
|
|
65
|
+
crate::messaging::leader_receiver::active_team_key(workspace, &state_for_team);
|
|
64
66
|
(!key.is_empty()).then_some(key)
|
|
65
67
|
});
|
|
66
68
|
let mcp_config = adapter
|
|
@@ -72,8 +74,11 @@ pub(super) fn spawn_agent_window(
|
|
|
72
74
|
agent_id.as_str(),
|
|
73
75
|
team_id.as_deref().unwrap_or(""),
|
|
74
76
|
);
|
|
75
|
-
let mcp_config_path =
|
|
76
|
-
|
|
77
|
+
let mcp_config_path = crate::lifecycle::launch::write_worker_mcp_config(
|
|
78
|
+
workspace,
|
|
79
|
+
agent_id.as_str(),
|
|
80
|
+
&mcp_config,
|
|
81
|
+
)?;
|
|
77
82
|
let profile_launch =
|
|
78
83
|
crate::lifecycle::profile_launch::prepare_provider_profile_launch_from_json(
|
|
79
84
|
workspace,
|
|
@@ -81,11 +86,7 @@ pub(super) fn spawn_agent_window(
|
|
|
81
86
|
agent,
|
|
82
87
|
Some(&mcp_config),
|
|
83
88
|
)?;
|
|
84
|
-
let command_model = profile_launch
|
|
85
|
-
.command_overrides
|
|
86
|
-
.model
|
|
87
|
-
.as_deref()
|
|
88
|
-
.or(model);
|
|
89
|
+
let command_model = profile_launch.command_overrides.model.as_deref().or(model);
|
|
89
90
|
let context = crate::provider::ProviderCommandContext {
|
|
90
91
|
auth_mode,
|
|
91
92
|
mcp_config: Some(&mcp_config),
|
|
@@ -115,7 +116,9 @@ pub(super) fn spawn_agent_window(
|
|
|
115
116
|
agent_id.as_str(),
|
|
116
117
|
team_id.as_deref(),
|
|
117
118
|
);
|
|
118
|
-
let window =
|
|
119
|
+
let window = layout_placement
|
|
120
|
+
.map(|placement| placement.layout_window.clone())
|
|
121
|
+
.unwrap_or_else(|| WindowName::new(agent_id.as_str()));
|
|
119
122
|
let mut env = crate::lifecycle::launch::inherited_env_with_team_overrides(
|
|
120
123
|
workspace,
|
|
121
124
|
agent_id.as_str(),
|
|
@@ -133,7 +136,38 @@ pub(super) fn spawn_agent_window(
|
|
|
133
136
|
})
|
|
134
137
|
.unwrap_or(workspace);
|
|
135
138
|
let env_unset: Vec<String> = profile_launch.env_unset.iter().cloned().collect();
|
|
136
|
-
let result = if
|
|
139
|
+
let result = if let Some(placement) = layout_placement {
|
|
140
|
+
if placement.starts_window {
|
|
141
|
+
if into_existing_session {
|
|
142
|
+
transport.spawn_into_with_env_unset(
|
|
143
|
+
session_name,
|
|
144
|
+
&window,
|
|
145
|
+
&plan.argv,
|
|
146
|
+
spawn_cwd,
|
|
147
|
+
&env,
|
|
148
|
+
&env_unset,
|
|
149
|
+
)
|
|
150
|
+
} else {
|
|
151
|
+
transport.spawn_first_with_env_unset(
|
|
152
|
+
session_name,
|
|
153
|
+
&window,
|
|
154
|
+
&plan.argv,
|
|
155
|
+
spawn_cwd,
|
|
156
|
+
&env,
|
|
157
|
+
&env_unset,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
transport.spawn_split_with_env_unset(
|
|
162
|
+
session_name,
|
|
163
|
+
&window,
|
|
164
|
+
&plan.argv,
|
|
165
|
+
spawn_cwd,
|
|
166
|
+
&env,
|
|
167
|
+
&env_unset,
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
} else if into_existing_session {
|
|
137
171
|
transport.spawn_into_with_env_unset(
|
|
138
172
|
session_name,
|
|
139
173
|
&window,
|
|
@@ -163,6 +197,7 @@ pub(super) fn spawn_agent_window(
|
|
|
163
197
|
spawn,
|
|
164
198
|
plan,
|
|
165
199
|
profile_launch,
|
|
200
|
+
layout_placement: layout_placement.cloned(),
|
|
166
201
|
})
|
|
167
202
|
}
|
|
168
203
|
|
|
@@ -252,19 +287,30 @@ pub(super) fn resume_backing_exists_for_agent(
|
|
|
252
287
|
rollout_path: Option<&RolloutPath>,
|
|
253
288
|
) -> bool {
|
|
254
289
|
match provider {
|
|
290
|
+
provider if !provider_supports_resume(provider) => {
|
|
291
|
+
let _ = (workspace, agent_id, agent, session_id, rollout_path);
|
|
292
|
+
false
|
|
293
|
+
}
|
|
255
294
|
Provider::Codex => rollout_path_exists(rollout_path),
|
|
256
295
|
Provider::Claude | Provider::ClaudeCode => {
|
|
257
296
|
rollout_path_exists(rollout_path)
|
|
258
297
|
|| event_log_transcript_exists(workspace, agent_id.as_str(), session_id.as_str())
|
|
259
298
|
}
|
|
260
299
|
Provider::Copilot => copilot_session_store_has_session(session_id.as_str()),
|
|
261
|
-
Provider::GeminiCli | Provider::Fake =>
|
|
262
|
-
let _ = agent;
|
|
263
|
-
true
|
|
264
|
-
}
|
|
300
|
+
Provider::GeminiCli | Provider::Fake => false,
|
|
265
301
|
}
|
|
266
302
|
}
|
|
267
303
|
|
|
304
|
+
pub(super) fn provider_supports_resume(provider: Provider) -> bool {
|
|
305
|
+
crate::provider::get_adapter(provider).caps().resume
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
pub(super) fn provider_wire_supports_resume(provider: &str) -> bool {
|
|
309
|
+
parse_provider(provider)
|
|
310
|
+
.map(provider_supports_resume)
|
|
311
|
+
.unwrap_or(false)
|
|
312
|
+
}
|
|
313
|
+
|
|
268
314
|
fn rollout_path_exists(rollout_path: Option<&RolloutPath>) -> bool {
|
|
269
315
|
rollout_path
|
|
270
316
|
.as_ref()
|
|
@@ -308,13 +354,12 @@ fn copilot_session_store_has_session(session_id: &str) -> bool {
|
|
|
308
354
|
) else {
|
|
309
355
|
return false;
|
|
310
356
|
};
|
|
311
|
-
conn
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
.is_ok()
|
|
357
|
+
conn.query_row(
|
|
358
|
+
"select 1 from sessions where id = ?1 limit 1",
|
|
359
|
+
[session_id],
|
|
360
|
+
|_| Ok(()),
|
|
361
|
+
)
|
|
362
|
+
.is_ok()
|
|
318
363
|
}
|
|
319
364
|
|
|
320
365
|
pub(crate) fn refresh_missing_provider_sessions(
|
|
@@ -469,7 +514,10 @@ pub(super) fn load_team_spec(workspace: &Path) -> Result<YamlValue, LifecycleErr
|
|
|
469
514
|
yaml::loads(&text).map_err(|e| LifecycleError::Compile(e.to_string()))
|
|
470
515
|
}
|
|
471
516
|
|
|
472
|
-
pub(super) fn find_spec_agent<'a>(
|
|
517
|
+
pub(super) fn find_spec_agent<'a>(
|
|
518
|
+
spec: &'a YamlValue,
|
|
519
|
+
agent_id: &AgentId,
|
|
520
|
+
) -> Option<&'a YamlValue> {
|
|
473
521
|
let leader_is_agent = spec
|
|
474
522
|
.get("leader")
|
|
475
523
|
.and_then(|v| v.get("id"))
|
|
@@ -479,23 +527,23 @@ pub(super) fn find_spec_agent<'a>(spec: &'a YamlValue, agent_id: &AgentId) -> Op
|
|
|
479
527
|
if leader_is_agent {
|
|
480
528
|
return None;
|
|
481
529
|
}
|
|
482
|
-
spec.get("agents")
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
.map(|id| id == agent_id.as_str())
|
|
490
|
-
.unwrap_or(false)
|
|
491
|
-
})
|
|
530
|
+
spec.get("agents")?.as_list()?.iter().find(|agent| {
|
|
531
|
+
agent
|
|
532
|
+
.get("id")
|
|
533
|
+
.and_then(YamlValue::as_str)
|
|
534
|
+
.map(|id| id == agent_id.as_str())
|
|
535
|
+
.unwrap_or(false)
|
|
536
|
+
})
|
|
492
537
|
}
|
|
493
538
|
|
|
494
539
|
pub(super) fn unknown_worker(agent_id: &AgentId) -> LifecycleError {
|
|
495
540
|
LifecycleError::RequirementUnmet(format!("unknown worker agent id: {agent_id}"))
|
|
496
541
|
}
|
|
497
542
|
|
|
498
|
-
pub(super) fn state_session_name_from_spec(
|
|
543
|
+
pub(super) fn state_session_name_from_spec(
|
|
544
|
+
state: &serde_json::Value,
|
|
545
|
+
spec: &YamlValue,
|
|
546
|
+
) -> SessionName {
|
|
499
547
|
state
|
|
500
548
|
.get("session_name")
|
|
501
549
|
.and_then(|v| v.as_str())
|
|
@@ -709,9 +757,7 @@ pub(super) fn agent_is_running(
|
|
|
709
757
|
agent_id: &AgentId,
|
|
710
758
|
transport: &dyn crate::transport::Transport,
|
|
711
759
|
) -> bool {
|
|
712
|
-
let agent_state = state
|
|
713
|
-
.get("agents")
|
|
714
|
-
.and_then(|v| v.get(agent_id.as_str()));
|
|
760
|
+
let agent_state = state.get("agents").and_then(|v| v.get(agent_id.as_str()));
|
|
715
761
|
let status = agent_state
|
|
716
762
|
.and_then(|v| v.get("status"))
|
|
717
763
|
.and_then(|v| v.as_str())
|