@team-agent/installer 0.5.49 → 0.5.51
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 +6 -4
- package/crates/team-agent/src/cli/emit.rs +91 -39
- package/crates/team-agent/src/cli/mod.rs +33 -15
- package/crates/team-agent/src/cli/named_address.rs +82 -53
- package/crates/team-agent/src/cli/send/coordinator.rs +163 -0
- package/crates/team-agent/src/cli/send/mailbox.rs +99 -0
- package/crates/team-agent/src/cli/send/persist.rs +154 -0
- package/crates/team-agent/src/cli/send/presentation.rs +333 -0
- package/crates/team-agent/src/cli/send/resolve.rs +361 -0
- package/crates/team-agent/src/cli/send.rs +103 -1308
- package/crates/team-agent/src/cli/spec.rs +2 -2
- package/crates/team-agent/src/cli/status_port/agents.rs +358 -0
- package/crates/team-agent/src/cli/status_port/approvals.rs +79 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +207 -0
- package/crates/team-agent/src/cli/status_port/format.rs +145 -0
- package/crates/team-agent/src/cli/status_port/inbox.rs +36 -0
- package/crates/team-agent/src/cli/status_port/runtime.rs +195 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +181 -0
- package/crates/team-agent/src/cli/status_port/store.rs +412 -0
- package/crates/team-agent/src/cli/status_port/tests.rs +54 -0
- package/crates/team-agent/src/cli/status_port.rs +47 -1548
- package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -1
- package/crates/team-agent/src/cli/tests/named_address.rs +9 -7
- package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -3
- package/crates/team-agent/src/cli/tests/status_send.rs +17 -33
- package/crates/team-agent/src/cli/types.rs +5 -8
- package/crates/team-agent/src/coordinator/conpty_shim.rs +34 -30
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +135 -13
- package/crates/team-agent/src/coordinator/tick.rs +37 -0
- package/crates/team-agent/src/db/agent_health_capture.rs +18 -13
- package/crates/team-agent/src/db/message_store.rs +154 -44
- package/crates/team-agent/src/event_log.rs +73 -0
- package/crates/team-agent/src/leader/start.rs +28 -4
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +424 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +297 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +160 -0
- package/crates/team-agent/src/lifecycle/launch/approval.rs +134 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +492 -0
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +297 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +372 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +313 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +478 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +201 -0
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +477 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +278 -0
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +123 -0
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +377 -0
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +434 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +499 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +438 -0
- package/crates/team-agent/src/lifecycle/launch.rs +119 -5351
- package/crates/team-agent/src/lifecycle/restart/agent.rs +44 -26
- package/crates/team-agent/src/lifecycle/restart/common.rs +53 -27
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +67 -26
- package/crates/team-agent/src/lifecycle/restart/remove.rs +435 -72
- package/crates/team-agent/src/lifecycle/restart.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +575 -17
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +55 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +24 -1
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +1 -1
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +30 -7
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +8 -4
- package/crates/team-agent/src/mcp_server/mod.rs +2 -2
- package/crates/team-agent/src/mcp_server/tests/send.rs +22 -15
- package/crates/team-agent/src/mcp_server/tests/wire.rs +6 -0
- package/crates/team-agent/src/mcp_server/tools.rs +26 -15
- package/crates/team-agent/src/mcp_server/wire.rs +2 -18
- package/crates/team-agent/src/messaging/activity.rs +4 -2
- package/crates/team-agent/src/messaging/address.rs +86 -0
- package/crates/team-agent/src/messaging/delivery.rs +165 -39
- package/crates/team-agent/src/messaging/helpers.rs +17 -13
- package/crates/team-agent/src/messaging/leader_receiver.rs +60 -35
- package/crates/team-agent/src/messaging/mod.rs +11 -2
- package/crates/team-agent/src/messaging/persist.rs +309 -0
- package/crates/team-agent/src/messaging/results.rs +16 -24
- package/crates/team-agent/src/messaging/scheduler.rs +4 -2
- package/crates/team-agent/src/messaging/selftest.rs +19 -12
- package/crates/team-agent/src/messaging/send.rs +133 -58
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +305 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +38 -17
- package/crates/team-agent/src/messaging/watchers.rs +13 -3
- package/crates/team-agent/src/redaction.rs +72 -2
- package/crates/team-agent/src/state/persist.rs +2 -1
- package/crates/team-agent/src/state/repository/tests.rs +47 -0
- package/crates/team-agent/src/state/repository.rs +59 -16
- package/package.json +4 -4
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
use std::collections::{BTreeMap, BTreeSet};
|
|
2
|
+
use std::path::{Path, PathBuf};
|
|
3
|
+
use std::process::Command;
|
|
4
|
+
|
|
5
|
+
use crate::lifecycle::*;
|
|
6
|
+
use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
|
|
7
|
+
use crate::model::ids::AgentId;
|
|
8
|
+
use crate::model::permissions::{self, AgentPermissionInput};
|
|
9
|
+
use crate::model::yaml::{self, Value};
|
|
10
|
+
use crate::state::persist::load_runtime_state;
|
|
11
|
+
use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
|
|
12
|
+
|
|
13
|
+
use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
|
|
14
|
+
|
|
15
|
+
use super::*;
|
|
16
|
+
|
|
17
|
+
pub(crate) fn quick_start_tmux_backend(workspace: &Path) -> crate::tmux_backend::TmuxBackend {
|
|
18
|
+
if let Some(endpoint) = crate::tmux_backend::socket_name_from_tmux_env() {
|
|
19
|
+
crate::tmux_backend::TmuxBackend::for_tmux_endpoint(&endpoint)
|
|
20
|
+
} else {
|
|
21
|
+
crate::tmux_backend::TmuxBackend::for_workspace(workspace)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
pub(crate) fn selected_tmux_socket_source(
|
|
26
|
+
transport: &dyn Transport,
|
|
27
|
+
workspace: &Path,
|
|
28
|
+
) -> Option<&'static str> {
|
|
29
|
+
let endpoint = transport.tmux_endpoint()?;
|
|
30
|
+
if crate::tmux_backend::socket_name_from_tmux_env().as_deref() == Some(endpoint.as_str()) {
|
|
31
|
+
Some("leader_env")
|
|
32
|
+
} else if endpoint == crate::tmux_backend::socket_name_for_workspace(workspace) {
|
|
33
|
+
Some("workspace")
|
|
34
|
+
} else {
|
|
35
|
+
None
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub(crate) fn configure_adaptive_pane_title(
|
|
40
|
+
workspace: &Path,
|
|
41
|
+
transport: &dyn Transport,
|
|
42
|
+
session_name: &SessionName,
|
|
43
|
+
window: &WindowName,
|
|
44
|
+
pane: &PaneId,
|
|
45
|
+
agent_id: &str,
|
|
46
|
+
) {
|
|
47
|
+
if let Err(error) =
|
|
48
|
+
transport.configure_adaptive_pane_title(session_name, window, pane, agent_id)
|
|
49
|
+
{
|
|
50
|
+
let message = format!("adaptive layout pane title failed for {agent_id}: {error}");
|
|
51
|
+
eprintln!("Warning: {message}");
|
|
52
|
+
if let Err(event_error) = crate::event_log::EventLog::new(workspace).write(
|
|
53
|
+
"adaptive_layout.pane_title_failed",
|
|
54
|
+
serde_json::json!({
|
|
55
|
+
"agent_id": agent_id,
|
|
56
|
+
"session": session_name.as_str(),
|
|
57
|
+
"window": window.as_str(),
|
|
58
|
+
"pane_id": pane.as_str(),
|
|
59
|
+
"warning": message,
|
|
60
|
+
}),
|
|
61
|
+
) {
|
|
62
|
+
eprintln!(
|
|
63
|
+
"Warning: adaptive_layout.pane_title_failed event write failed: {event_error}"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
pub(super) fn explicit_quick_start_workspace(workspace: &Path) -> PathBuf {
|
|
70
|
+
std::fs::canonicalize(workspace).unwrap_or_else(|_| {
|
|
71
|
+
if workspace.is_absolute() {
|
|
72
|
+
workspace.to_path_buf()
|
|
73
|
+
} else {
|
|
74
|
+
std::env::current_dir()
|
|
75
|
+
.unwrap_or_else(|_| PathBuf::from("."))
|
|
76
|
+
.join(workspace)
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// `quick_start` with an injected transport — tests inject a recording mock so the REAL spawn path
|
|
82
|
+
/// (launch dry_run=false → spawn_agents) is asserted without a live tmux; prod uses the real TmuxBackend.
|
|
83
|
+
/// Annotate `state.tmux_endpoint` / `state.tmux_socket` (and `tmux_socket_source`)
|
|
84
|
+
/// from the active transport. Originally only called at `launch_with_transport`
|
|
85
|
+
/// init time; **`0.3.24` opened this to `pub(crate)` so restart/add/fork can keep
|
|
86
|
+
/// the persisted endpoint synchronized with the transport they actually used,
|
|
87
|
+
/// closing the silent socket-drift gap** (single state-save path; no parallel
|
|
88
|
+
/// "annotate after spawn" race with coordinator).
|
|
89
|
+
/// 0.5.x Phase 1d Batch 2: generic runtime-transport annotator.
|
|
90
|
+
///
|
|
91
|
+
/// Writes `state.transport = { kind, source }` for every backend
|
|
92
|
+
/// (kind = wire string `"tmux"` | `"conpty"`; source = wire string
|
|
93
|
+
/// from `ResolvedTransport.source` when known, else `"unknown"`).
|
|
94
|
+
///
|
|
95
|
+
/// For tmux, ALSO forwards to `annotate_runtime_tmux_endpoint` so the
|
|
96
|
+
/// existing `tmux_endpoint` / `tmux_socket` / `tmux_socket_source`
|
|
97
|
+
/// fields remain populated byte-equivalent to today's shape.
|
|
98
|
+
///
|
|
99
|
+
/// The tmux-specific fields are NOT written for ConPTY (CR C-4: no
|
|
100
|
+
/// tmux-only fields under a conpty state; keeps compact status
|
|
101
|
+
/// stable). ConPTY discovery fields (`pipe_name`, `shim_pid`) are
|
|
102
|
+
/// added in Batch 3 when the shim boot path lands; this function just
|
|
103
|
+
/// pins the top-level `transport.kind`/`source`.
|
|
104
|
+
pub fn annotate_runtime_transport(
|
|
105
|
+
state: &mut serde_json::Value,
|
|
106
|
+
transport: &dyn Transport,
|
|
107
|
+
workspace: &Path,
|
|
108
|
+
source: Option<&str>,
|
|
109
|
+
) {
|
|
110
|
+
use crate::transport::BackendKind;
|
|
111
|
+
let kind_wire = match transport.kind() {
|
|
112
|
+
BackendKind::Tmux => "tmux",
|
|
113
|
+
BackendKind::WezTerm => "wezterm",
|
|
114
|
+
BackendKind::ConPty => "conpty",
|
|
115
|
+
};
|
|
116
|
+
if let Some(obj) = state.as_object_mut() {
|
|
117
|
+
let transport_block = serde_json::json!({
|
|
118
|
+
"kind": kind_wire,
|
|
119
|
+
"source": source.unwrap_or("unknown"),
|
|
120
|
+
});
|
|
121
|
+
obj.insert("transport".to_string(), transport_block);
|
|
122
|
+
}
|
|
123
|
+
// Tmux: preserve the existing tmux endpoint annotation shape.
|
|
124
|
+
if matches!(transport.kind(), BackendKind::Tmux) {
|
|
125
|
+
annotate_runtime_tmux_endpoint(state, transport, workspace);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
pub(crate) fn annotate_runtime_tmux_endpoint(
|
|
130
|
+
state: &mut serde_json::Value,
|
|
131
|
+
transport: &dyn Transport,
|
|
132
|
+
workspace: &Path,
|
|
133
|
+
) {
|
|
134
|
+
let Some(endpoint) = transport.tmux_endpoint() else {
|
|
135
|
+
return;
|
|
136
|
+
};
|
|
137
|
+
let endpoint_for_state = if Path::new(&endpoint).is_absolute() || endpoint == "default" {
|
|
138
|
+
endpoint.clone()
|
|
139
|
+
} else if endpoint == crate::tmux_backend::socket_name_for_workspace(workspace) {
|
|
140
|
+
crate::tmux_backend::socket_path_for_workspace(workspace)
|
|
141
|
+
.map(|path| path.to_string_lossy().to_string())
|
|
142
|
+
.unwrap_or_else(|| endpoint.clone())
|
|
143
|
+
} else {
|
|
144
|
+
crate::tmux_backend::socket_path_for_name(&endpoint)
|
|
145
|
+
.map(|path| path.to_string_lossy().to_string())
|
|
146
|
+
.unwrap_or_else(|| endpoint.clone())
|
|
147
|
+
};
|
|
148
|
+
if let Some(obj) = state.as_object_mut() {
|
|
149
|
+
obj.insert(
|
|
150
|
+
"tmux_endpoint".to_string(),
|
|
151
|
+
serde_json::json!(endpoint_for_state),
|
|
152
|
+
);
|
|
153
|
+
obj.insert(
|
|
154
|
+
"tmux_socket".to_string(),
|
|
155
|
+
obj.get("tmux_endpoint")
|
|
156
|
+
.cloned()
|
|
157
|
+
.unwrap_or(serde_json::Value::Null),
|
|
158
|
+
);
|
|
159
|
+
if let Some(source) = selected_tmux_socket_source(transport, workspace) {
|
|
160
|
+
obj.insert("tmux_socket_source".to_string(), serde_json::json!(source));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pub(super) fn attach_commands_for_runtime_windows<'a>(
|
|
166
|
+
endpoint: Option<&str>,
|
|
167
|
+
workspace: &Path,
|
|
168
|
+
session_name: &SessionName,
|
|
169
|
+
window_names: impl IntoIterator<Item = &'a str>,
|
|
170
|
+
) -> Vec<String> {
|
|
171
|
+
let windows = window_names.into_iter().collect::<Vec<_>>();
|
|
172
|
+
let attach = if let Some(endpoint) = endpoint.filter(|endpoint| !endpoint.is_empty()) {
|
|
173
|
+
if Path::new(endpoint).is_absolute() {
|
|
174
|
+
windows
|
|
175
|
+
.iter()
|
|
176
|
+
.map(|window_name| {
|
|
177
|
+
format!(
|
|
178
|
+
"tmux -S {} attach -t {}:{}",
|
|
179
|
+
endpoint,
|
|
180
|
+
session_name.as_str(),
|
|
181
|
+
window_name
|
|
182
|
+
)
|
|
183
|
+
})
|
|
184
|
+
.collect::<Vec<_>>()
|
|
185
|
+
} else {
|
|
186
|
+
crate::tmux_backend::attach_commands_for_windows(
|
|
187
|
+
workspace,
|
|
188
|
+
session_name,
|
|
189
|
+
windows.iter().copied(),
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
crate::tmux_backend::attach_commands_for_windows(
|
|
194
|
+
workspace,
|
|
195
|
+
session_name,
|
|
196
|
+
windows.iter().copied(),
|
|
197
|
+
)
|
|
198
|
+
};
|
|
199
|
+
attach
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
pub(super) fn started_attach_window_names(started: &[StartedAgent]) -> Vec<String> {
|
|
203
|
+
let mut windows = started
|
|
204
|
+
.iter()
|
|
205
|
+
.map(|started| {
|
|
206
|
+
started
|
|
207
|
+
.layout_window
|
|
208
|
+
.as_ref()
|
|
209
|
+
.map(|window| window.as_str().to_string())
|
|
210
|
+
.unwrap_or_else(|| started.agent_id.as_str().to_string())
|
|
211
|
+
})
|
|
212
|
+
.collect::<Vec<_>>();
|
|
213
|
+
windows.sort();
|
|
214
|
+
windows.dedup();
|
|
215
|
+
windows
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
pub(crate) fn attach_window_names_for_state_agents<'a>(
|
|
219
|
+
state: &serde_json::Value,
|
|
220
|
+
agent_ids: impl IntoIterator<Item = &'a str>,
|
|
221
|
+
) -> Vec<String> {
|
|
222
|
+
let windows = agent_ids
|
|
223
|
+
.into_iter()
|
|
224
|
+
.map(|agent_id| {
|
|
225
|
+
state
|
|
226
|
+
.get("agents")
|
|
227
|
+
.and_then(serde_json::Value::as_object)
|
|
228
|
+
.and_then(|agents| agents.get(agent_id))
|
|
229
|
+
.and_then(|agent| {
|
|
230
|
+
agent
|
|
231
|
+
.get("layout_window")
|
|
232
|
+
.or_else(|| agent.get("window"))
|
|
233
|
+
.and_then(serde_json::Value::as_str)
|
|
234
|
+
.filter(|window| !window.is_empty())
|
|
235
|
+
})
|
|
236
|
+
.unwrap_or(agent_id)
|
|
237
|
+
.to_string()
|
|
238
|
+
})
|
|
239
|
+
.collect::<Vec<_>>();
|
|
240
|
+
attach_window_names_with_managed_leader(state, windows)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
pub(super) fn quick_start_attach_window_names(state: &serde_json::Value) -> Vec<String> {
|
|
244
|
+
let windows = state
|
|
245
|
+
.get("agents")
|
|
246
|
+
.and_then(serde_json::Value::as_object)
|
|
247
|
+
.map(|agents| {
|
|
248
|
+
agents
|
|
249
|
+
.iter()
|
|
250
|
+
.filter_map(|(agent_id, agent)| {
|
|
251
|
+
agent
|
|
252
|
+
.get("window")
|
|
253
|
+
.and_then(serde_json::Value::as_str)
|
|
254
|
+
.filter(|window| !window.is_empty())
|
|
255
|
+
.map(str::to_string)
|
|
256
|
+
.or_else(|| Some(agent_id.clone()))
|
|
257
|
+
})
|
|
258
|
+
.collect::<Vec<_>>()
|
|
259
|
+
})
|
|
260
|
+
.unwrap_or_default();
|
|
261
|
+
attach_window_names_with_managed_leader(state, windows)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
pub(super) fn attach_window_names_with_managed_leader(
|
|
265
|
+
state: &serde_json::Value,
|
|
266
|
+
mut windows: Vec<String>,
|
|
267
|
+
) -> Vec<String> {
|
|
268
|
+
if state_uses_managed_leader(state) {
|
|
269
|
+
windows.push("leader".to_string());
|
|
270
|
+
}
|
|
271
|
+
windows.sort();
|
|
272
|
+
windows.dedup();
|
|
273
|
+
windows
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
pub(super) fn state_uses_managed_leader(state: &serde_json::Value) -> bool {
|
|
277
|
+
crate::state::projection::state_is_managed_leader(state)
|
|
278
|
+
}
|
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
//! Future commits migrate the inline phase fns at launch.rs:2928-2944
|
|
5
5
|
//! here.
|
|
6
6
|
|
|
7
|
+
use std::collections::{BTreeMap, BTreeSet};
|
|
8
|
+
use std::path::{Path, PathBuf};
|
|
9
|
+
use std::process::Command;
|
|
10
|
+
|
|
11
|
+
use crate::lifecycle::*;
|
|
12
|
+
use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
|
|
13
|
+
use crate::model::ids::AgentId;
|
|
14
|
+
use crate::model::permissions::{self, AgentPermissionInput};
|
|
15
|
+
use crate::model::yaml::{self, Value};
|
|
16
|
+
use crate::state::persist::load_runtime_state;
|
|
17
|
+
use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
|
|
18
|
+
|
|
19
|
+
use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
|
|
20
|
+
|
|
21
|
+
use super::*;
|
|
22
|
+
|
|
7
23
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
8
24
|
pub enum ReadinessPhase {
|
|
9
25
|
StartCoordinator,
|
|
@@ -30,3 +46,110 @@ mod tests {
|
|
|
30
46
|
.starts_with("launch.readiness."));
|
|
31
47
|
}
|
|
32
48
|
}
|
|
49
|
+
|
|
50
|
+
pub(super) fn quick_start_worker_readiness(
|
|
51
|
+
workspace: &Path,
|
|
52
|
+
team_key: &str,
|
|
53
|
+
) -> QuickStartReadiness {
|
|
54
|
+
let Ok(state) = load_runtime_state(workspace) else {
|
|
55
|
+
return QuickStartReadiness::PendingToolLoad;
|
|
56
|
+
};
|
|
57
|
+
let team_state = state
|
|
58
|
+
.get("teams")
|
|
59
|
+
.and_then(serde_json::Value::as_object)
|
|
60
|
+
.and_then(|teams| teams.get(team_key))
|
|
61
|
+
.unwrap_or(&state);
|
|
62
|
+
let Some(agents) = team_state
|
|
63
|
+
.get("agents")
|
|
64
|
+
.and_then(serde_json::Value::as_object)
|
|
65
|
+
else {
|
|
66
|
+
return QuickStartReadiness::PendingToolLoad;
|
|
67
|
+
};
|
|
68
|
+
let all_spawned = !agents.is_empty();
|
|
69
|
+
let leader_receiver_attached = launched_team_receiver_is_attached(workspace, team_key);
|
|
70
|
+
let all_attached_receiver = leader_receiver_attached;
|
|
71
|
+
let mut unhealthy: Vec<String> = agents
|
|
72
|
+
.iter()
|
|
73
|
+
.filter_map(|(id, agent)| {
|
|
74
|
+
let status = agent.get("status").and_then(serde_json::Value::as_str);
|
|
75
|
+
match status {
|
|
76
|
+
Some("running") => None,
|
|
77
|
+
_ => Some(id.clone()),
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
.collect();
|
|
81
|
+
if !unhealthy.is_empty() {
|
|
82
|
+
unhealthy.sort();
|
|
83
|
+
unhealthy.dedup();
|
|
84
|
+
QuickStartReadiness::Degraded {
|
|
85
|
+
unhealthy_agents: unhealthy,
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
let incomplete_agents =
|
|
89
|
+
crate::session_capture::incomplete_interacted_resumable_agent_ids(team_state);
|
|
90
|
+
let all_resumable_have_session = incomplete_agents.is_empty();
|
|
91
|
+
let _readiness_ready = all_spawned && all_attached_receiver && all_resumable_have_session;
|
|
92
|
+
QuickStartReadiness::PendingToolLoad
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pub(super) fn quick_start_session_capture_incomplete_agents(
|
|
97
|
+
workspace: &Path,
|
|
98
|
+
team_key: &str,
|
|
99
|
+
) -> Vec<String> {
|
|
100
|
+
let Ok(state) = load_runtime_state(workspace) else {
|
|
101
|
+
return Vec::new();
|
|
102
|
+
};
|
|
103
|
+
let team_state = state
|
|
104
|
+
.get("teams")
|
|
105
|
+
.and_then(serde_json::Value::as_object)
|
|
106
|
+
.and_then(|teams| teams.get(team_key))
|
|
107
|
+
.unwrap_or(&state);
|
|
108
|
+
crate::session_capture::incomplete_interacted_resumable_agent_ids(team_state)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
pub(crate) fn launched_team_receiver_is_attached(workspace: &Path, team_key: &str) -> bool {
|
|
112
|
+
let Ok(state) = load_runtime_state(workspace) else {
|
|
113
|
+
return true;
|
|
114
|
+
};
|
|
115
|
+
let team_state = state
|
|
116
|
+
.get("teams")
|
|
117
|
+
.and_then(serde_json::Value::as_object)
|
|
118
|
+
.and_then(|teams| teams.get(team_key))
|
|
119
|
+
.unwrap_or(&state);
|
|
120
|
+
if team_state.get("leader_receiver").is_none() {
|
|
121
|
+
return crate::state::projection::state_is_external_leader(team_state);
|
|
122
|
+
}
|
|
123
|
+
if team_uses_fake_model_harness(team_state) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
leader_receiver_is_attached(team_state)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
pub(super) fn team_uses_fake_model_harness(team_state: &serde_json::Value) -> bool {
|
|
130
|
+
team_state
|
|
131
|
+
.get("agents")
|
|
132
|
+
.and_then(serde_json::Value::as_object)
|
|
133
|
+
.is_some_and(|agents| {
|
|
134
|
+
!agents.is_empty()
|
|
135
|
+
&& agents.values().all(|agent| {
|
|
136
|
+
agent.get("model").and_then(serde_json::Value::as_str) == Some("fake")
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
pub(super) fn leader_receiver_is_attached(team_state: &serde_json::Value) -> bool {
|
|
142
|
+
let Some(receiver) = team_state.get("leader_receiver") else {
|
|
143
|
+
return false;
|
|
144
|
+
};
|
|
145
|
+
let status = receiver
|
|
146
|
+
.get("status")
|
|
147
|
+
.and_then(serde_json::Value::as_str)
|
|
148
|
+
.unwrap_or("");
|
|
149
|
+
let pane_id = receiver
|
|
150
|
+
.get("pane_id")
|
|
151
|
+
.and_then(serde_json::Value::as_str)
|
|
152
|
+
.or_else(|| receiver.get("pane").and_then(serde_json::Value::as_str))
|
|
153
|
+
.unwrap_or("");
|
|
154
|
+
status == "attached" && !pane_id.is_empty() && pane_id != "__team_agent_unbound__"
|
|
155
|
+
}
|