@team-agent/installer 0.5.101 → 0.5.103

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.
Files changed (49) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/emit.rs +2 -1
  4. package/crates/team-agent/src/cli/models.rs +92 -226
  5. package/crates/team-agent/src/cli/spec.rs +1 -1
  6. package/crates/team-agent/src/cli/tests/main_preserved.rs +0 -74
  7. package/crates/team-agent/src/compiler/tests.rs +20 -23
  8. package/crates/team-agent/src/compiler.rs +49 -65
  9. package/crates/team-agent/src/coordinator/tests/s3_bounded.rs +16 -48
  10. package/crates/team-agent/src/leader/start.rs +1 -14
  11. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +37 -161
  12. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -1
  13. package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
  14. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +0 -28
  15. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +11 -9
  16. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +8 -401
  17. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +2 -2
  18. package/crates/team-agent/src/lifecycle/worker_command_context.rs +22 -8
  19. package/crates/team-agent/src/mcp_server/helpers.rs +0 -16
  20. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +2 -7
  21. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -127
  22. package/crates/team-agent/src/mcp_server/mod.rs +7 -12
  23. package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -132
  24. package/crates/team-agent/src/mcp_server/tests/send.rs +1 -93
  25. package/crates/team-agent/src/mcp_server/tests/tools.rs +0 -61
  26. package/crates/team-agent/src/mcp_server/tests/wire.rs +6 -51
  27. package/crates/team-agent/src/mcp_server/tests.rs +4 -200
  28. package/crates/team-agent/src/mcp_server/tools.rs +5 -396
  29. package/crates/team-agent/src/mcp_server/types.rs +3 -36
  30. package/crates/team-agent/src/mcp_server/wire.rs +2 -223
  31. package/crates/team-agent/src/messaging/leader_receiver.rs +1 -1
  32. package/crates/team-agent/src/messaging/mod.rs +2 -2
  33. package/crates/team-agent/src/messaging/results.rs +11 -42
  34. package/crates/team-agent/src/messaging/scheduler.rs +2 -205
  35. package/crates/team-agent/src/messaging/tests/basic.rs +0 -31
  36. package/crates/team-agent/src/messaging/tests/main_preserved.rs +0 -73
  37. package/crates/team-agent/src/messaging/tests/runtime.rs +0 -26
  38. package/crates/team-agent/src/messaging/types.rs +0 -20
  39. package/crates/team-agent/src/model/spec.rs +14 -18
  40. package/crates/team-agent/src/provider/mod.rs +1 -0
  41. package/crates/team-agent/src/provider/model_catalog.rs +650 -0
  42. package/crates/team-agent/src/state/repository/tests.rs +1 -16
  43. package/crates/team-agent/src/state/repository.rs +1 -33
  44. package/package.json +4 -4
  45. package/skills/team-agent/SKILL.md +5 -5
  46. package/skills/team-agent/command-coverage.json +7 -0
  47. package/skills/team-agent/references/team-agent-operator.md +24 -116
  48. package/skills/team-agent/references/team-in-team.md +4 -19
  49. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +0 -484
@@ -1,484 +0,0 @@
1
- use std::path::{Path, PathBuf};
2
-
3
- use serde_json::Value;
4
-
5
- use crate::lifecycle::{ResetAgentOutcome, ResetRefusal};
6
- use crate::model::ids::{AgentId, TeamKey};
7
- use crate::model::yaml::{self, Value as YamlValue};
8
-
9
- use super::super::helpers::{enum_value, object_fields, tool_runtime_error};
10
- use super::super::{ToolOk, ToolResult};
11
-
12
- pub(crate) fn stop_agent(
13
- workspace: &Path,
14
- owner_team: Option<&TeamKey>,
15
- agent_id: &str,
16
- ) -> ToolResult {
17
- let lifecycle_workspace = lifecycle_workspace(workspace, owner_team, true)?;
18
- let report = crate::lifecycle::stop_agent(
19
- &lifecycle_workspace,
20
- &AgentId::new(agent_id),
21
- owner_team.map(TeamKey::as_str),
22
- )
23
- .map_err(tool_runtime_error)?;
24
- Ok(ToolOk {
25
- fields: object_fields(serde_json::json!({
26
- "ok": true,
27
- "agent_id": report.agent_id.as_str(),
28
- "status": "stopped",
29
- "stopped": report.stopped,
30
- "target": report.target,
31
- "display_closed": report.display_closed,
32
- "state_file": report.state_file.to_string_lossy().to_string(),
33
- })),
34
- })
35
- }
36
-
37
- pub(crate) fn reset_agent(
38
- workspace: &Path,
39
- owner_team: Option<&TeamKey>,
40
- agent_id: &str,
41
- discard_session: bool,
42
- ) -> ToolResult {
43
- if !discard_session {
44
- return Ok(ToolOk {
45
- fields: object_fields(serde_json::json!({
46
- "ok": false,
47
- "agent_id": agent_id,
48
- "status": "refused",
49
- "reason": "discard_session_required",
50
- })),
51
- });
52
- }
53
- emit_newer_daemon_preserved_if_present(workspace)?;
54
- let lifecycle_workspace = lifecycle_workspace(workspace, owner_team, true)?;
55
- match crate::lifecycle::reset_agent(
56
- &lifecycle_workspace,
57
- &AgentId::new(agent_id),
58
- discard_session,
59
- false,
60
- owner_team.map(TeamKey::as_str),
61
- )
62
- .map_err(tool_runtime_error)?
63
- {
64
- ResetAgentOutcome::Reset {
65
- env,
66
- start_mode,
67
- discarded_session_id,
68
- session_id,
69
- new_session_id,
70
- capture_state,
71
- reset_proof,
72
- weak_reset_warning,
73
- } => Ok(ToolOk {
74
- fields: object_fields(serde_json::json!({
75
- "ok": true,
76
- "agent_id": env.agent_id.as_str(),
77
- "status": "reset",
78
- "state_file": env.state_file.to_string_lossy().to_string(),
79
- "coordinator_started": env.coordinator_started,
80
- "start_mode": enum_value(start_mode),
81
- "discarded_session_id": discarded_session_id.as_ref().map(|id| id.as_str()),
82
- "session_id": session_id.as_ref().map(|id| id.as_str()),
83
- "new_session_id": new_session_id.as_ref().map(|id| id.as_str()),
84
- "capture_state": match capture_state.as_str() {
85
- "captured" => "captured",
86
- "attribution_ambiguous" => "attribution_ambiguous",
87
- _ => "transcript_missing",
88
- },
89
- "reset_proof": if reset_proof == "weak" { "weak" } else { "strong" },
90
- "weak_reset_warning": weak_reset_warning,
91
- })),
92
- }),
93
- ResetAgentOutcome::Refused { reason } => Ok(ToolOk {
94
- fields: object_fields(serde_json::json!({
95
- "ok": false,
96
- "agent_id": agent_id,
97
- "status": "refused",
98
- "reason": reset_refusal_reason(reason),
99
- })),
100
- }),
101
- }
102
- }
103
-
104
- pub(crate) fn fork_agent(
105
- workspace: &Path,
106
- owner_team: Option<&TeamKey>,
107
- source_agent_id: &str,
108
- as_agent_id: &str,
109
- label: Option<&str>,
110
- ) -> ToolResult {
111
- emit_newer_daemon_preserved_if_present(workspace)?;
112
- let lifecycle_workspace = lifecycle_workspace(workspace, owner_team, false)?;
113
- // operations.py:315 — the label becomes the forked agent's role.
114
- let report = crate::lifecycle::launch::fork_agent(
115
- &lifecycle_workspace,
116
- &AgentId::new(source_agent_id),
117
- &AgentId::new(as_agent_id),
118
- label,
119
- false,
120
- owner_team.map(TeamKey::as_str),
121
- )
122
- .map_err(tool_runtime_error)?;
123
- Ok(ToolOk {
124
- fields: object_fields(serde_json::json!({
125
- "ok": true,
126
- "status": "forked",
127
- "source_agent_id": report.source_agent_id.as_str(),
128
- "agent_id": report.new_agent_id.as_str(),
129
- "new_agent_id": report.new_agent_id.as_str(),
130
- "state_file": report.env.state_file.to_string_lossy().to_string(),
131
- "coordinator_started": report.env.coordinator_started,
132
- "session_id": report.session_id.as_ref().map(|session| session.as_str()),
133
- "backing_state": report.backing_state,
134
- })),
135
- })
136
- }
137
-
138
- pub(crate) fn clone_agent(
139
- workspace: &Path,
140
- owner_team: Option<&TeamKey>,
141
- source_agent_id: &str,
142
- as_agent_id: &str,
143
- label: Option<&str>,
144
- ) -> ToolResult {
145
- emit_newer_daemon_preserved_if_present(workspace)?;
146
- let lifecycle_workspace = lifecycle_workspace(workspace, owner_team, false)?;
147
- let report = crate::lifecycle::launch::clone_agent(
148
- &lifecycle_workspace,
149
- &AgentId::new(source_agent_id),
150
- &AgentId::new(as_agent_id),
151
- label,
152
- false,
153
- owner_team.map(TeamKey::as_str),
154
- )
155
- .map_err(tool_runtime_error)?;
156
- Ok(ToolOk {
157
- fields: object_fields(serde_json::json!({
158
- "ok": true,
159
- "status": "cloned",
160
- "source_agent_id": report.source_agent_id.as_str(),
161
- "agent_id": report.new_agent_id.as_str(),
162
- "new_agent_id": report.new_agent_id.as_str(),
163
- "state_file": report.env.state_file.to_string_lossy().to_string(),
164
- "coordinator_started": report.env.coordinator_started,
165
- "session_id": report.session_id.as_ref().map(|session| session.as_str()),
166
- "new_session_id": report.session_id.as_ref().map(|session| session.as_str()),
167
- "backing_path": report.backing_path.as_ref().map(|path| path.to_string_lossy().to_string()),
168
- "backing_state": report.backing_state,
169
- })),
170
- })
171
- }
172
-
173
- fn reset_refusal_reason(reason: ResetRefusal) -> Value {
174
- match reason {
175
- ResetRefusal::DiscardSessionRequired => {
176
- Value::String("discard_session_required".to_string())
177
- }
178
- }
179
- }
180
-
181
- fn emit_newer_daemon_preserved_if_present(workspace: &Path) -> Result<(), super::super::ToolError> {
182
- let workspace_path = crate::coordinator::WorkspacePath::new(workspace.to_path_buf());
183
- let health = crate::coordinator::coordinator_health(&workspace_path);
184
- if !(health.service_available
185
- && !health.binary_identity_ok
186
- && matches!(
187
- health.binary_identity_relation,
188
- crate::coordinator::CoordinatorBinaryIdentityRelation::DaemonNewerThanCaller
189
- ))
190
- {
191
- return Ok(());
192
- }
193
- crate::event_log::EventLog::new(workspace)
194
- .write(
195
- "coordinator.newer_daemon_preserved",
196
- serde_json::json!({
197
- "pid": health.pid.map(|pid| pid.get()),
198
- "binary_identity_relation": health.binary_identity_relation.as_str(),
199
- "reason": "daemon_newer_than_caller",
200
- "daemon_binary_path": health
201
- .metadata
202
- .as_ref()
203
- .and_then(|metadata| metadata.binary_path.clone()),
204
- "daemon_binary_version": health
205
- .metadata
206
- .as_ref()
207
- .and_then(|metadata| metadata.binary_version.clone()),
208
- "caller_binary_path": health.current_binary_identity.binary_path,
209
- "caller_binary_version": health.current_binary_identity.binary_version,
210
- }),
211
- )
212
- .map(|_| ())
213
- .map_err(tool_runtime_error)
214
- }
215
-
216
- fn lifecycle_workspace(
217
- workspace: &Path,
218
- owner_team: Option<&TeamKey>,
219
- prepare_state: bool,
220
- ) -> Result<PathBuf, super::super::ToolError> {
221
- let team = owner_team.map(TeamKey::as_str);
222
- if let Ok(raw_state) = load_local_runtime_state(workspace) {
223
- if let Some(path) = state_spec_workspace(&raw_state, team) {
224
- return Ok(path);
225
- }
226
- if raw_state.get("agents").is_some() || raw_state.get("teams").is_some() {
227
- return materialize_mcp_lifecycle_spec(workspace, raw_state, team, prepare_state);
228
- }
229
- }
230
- if let Ok(selected) = crate::state::selector::resolve_active_team(
231
- workspace,
232
- team,
233
- crate::state::selector::SelectorMode::RequireSpec,
234
- ) {
235
- if let Some(path) = selected.spec_workspace {
236
- return Ok(path);
237
- }
238
- }
239
- let state = crate::state::projection::select_runtime_state(workspace, team)
240
- .map_err(tool_runtime_error)?;
241
- if let Some(path) = state_spec_workspace(&state, team) {
242
- return Ok(path);
243
- }
244
- Ok(workspace.to_path_buf())
245
- }
246
-
247
- fn state_spec_workspace(state: &Value, team: Option<&str>) -> Option<PathBuf> {
248
- if let Some(team) = team {
249
- if let Some(entry) = state
250
- .get("teams")
251
- .and_then(Value::as_object)
252
- .and_then(|teams| teams.get(team))
253
- {
254
- if let Some(path) = state_spec_workspace_from_entry(entry) {
255
- return Some(path);
256
- }
257
- }
258
- }
259
- state_spec_workspace_from_entry(state).or_else(|| {
260
- let teams = state.get("teams").and_then(Value::as_object)?;
261
- if teams.len() == 1 {
262
- teams
263
- .values()
264
- .next()
265
- .and_then(state_spec_workspace_from_entry)
266
- } else {
267
- None
268
- }
269
- })
270
- }
271
-
272
- fn state_spec_workspace_from_entry(state: &Value) -> Option<PathBuf> {
273
- state
274
- .get("spec_path")
275
- .and_then(Value::as_str)
276
- .filter(|s| !s.is_empty())
277
- .and_then(|s| Path::new(s).parent().map(Path::to_path_buf))
278
- .filter(|p| p.join("team.spec.yaml").exists())
279
- .or_else(|| {
280
- state
281
- .get("team_dir")
282
- .and_then(Value::as_str)
283
- .filter(|s| !s.is_empty())
284
- .map(PathBuf::from)
285
- .filter(|p| p.join("team.spec.yaml").exists())
286
- })
287
- }
288
-
289
- fn load_local_runtime_state(workspace: &Path) -> Result<Value, super::super::ToolError> {
290
- let path = crate::state::persist::runtime_state_path(workspace);
291
- match crate::state::repository::StateRepository::new(workspace)
292
- .load_workspace_if_exists_without_migrations()
293
- {
294
- Ok(Some(state)) => Ok(state),
295
- Ok(None) => Err(tool_runtime_error(format!(
296
- "read local runtime state {}: {}",
297
- path.display(),
298
- std::io::Error::from(std::io::ErrorKind::NotFound)
299
- ))),
300
- Err(crate::state::StateError::Json(error)) => Err(tool_runtime_error(format!(
301
- "parse local runtime state {}: {error}",
302
- path.display()
303
- ))),
304
- Err(crate::state::StateError::Io(error)) => Err(tool_runtime_error(format!(
305
- "read local runtime state {}: {error}",
306
- path.display()
307
- ))),
308
- Err(error) => Err(tool_runtime_error(format!(
309
- "read local runtime state {}: {error}",
310
- path.display()
311
- ))),
312
- }
313
- }
314
-
315
- fn materialize_mcp_lifecycle_spec(
316
- workspace: &Path,
317
- mut state: Value,
318
- team: Option<&str>,
319
- prepare_state: bool,
320
- ) -> Result<PathBuf, super::super::ToolError> {
321
- let team_name = team
322
- .filter(|s| !s.is_empty())
323
- .or_else(|| state.get("active_team_key").and_then(Value::as_str))
324
- .unwrap_or("team");
325
- let team_name = team_name.to_string();
326
- let Some(agents) = selected_agents(&state, team) else {
327
- return Ok(workspace.to_path_buf());
328
- };
329
- let mut agent_items = Vec::new();
330
- let top_agents = state.get("agents").and_then(Value::as_object);
331
- for (agent_id, agent_state) in agents {
332
- let provider = agent_state
333
- .get("provider")
334
- .and_then(Value::as_str)
335
- .or_else(|| {
336
- top_agents
337
- .and_then(|all| all.get(agent_id))
338
- .and_then(|agent| agent.get("provider"))
339
- .and_then(Value::as_str)
340
- })
341
- .unwrap_or("fake");
342
- agent_items.push(YamlValue::Map(vec![
343
- ("id".to_string(), YamlValue::Str(agent_id.clone())),
344
- ("provider".to_string(), YamlValue::Str(provider.to_string())),
345
- ("role".to_string(), YamlValue::Str("Worker".to_string())),
346
- ]));
347
- }
348
- if agent_items.is_empty() {
349
- return Ok(workspace.to_path_buf());
350
- }
351
- let spec = YamlValue::Map(vec![
352
- (
353
- "team".to_string(),
354
- YamlValue::Map(vec![
355
- ("name".to_string(), YamlValue::Str(team_name.clone())),
356
- (
357
- "objective".to_string(),
358
- YamlValue::Str("MCP lifecycle state-backed team".to_string()),
359
- ),
360
- ]),
361
- ),
362
- (
363
- "leader".to_string(),
364
- YamlValue::Map(vec![(
365
- "provider".to_string(),
366
- YamlValue::Str("codex".to_string()),
367
- )]),
368
- ),
369
- ("agents".to_string(), YamlValue::List(agent_items)),
370
- ]);
371
- let spec_workspace = workspace.join(".team").join(&team_name);
372
- std::fs::create_dir_all(&spec_workspace).map_err(|e| {
373
- tool_runtime_error(format!(
374
- "create MCP lifecycle spec dir {}: {e}",
375
- spec_workspace.display()
376
- ))
377
- })?;
378
- let spec_path = spec_workspace.join("team.spec.yaml");
379
- std::fs::write(&spec_path, yaml::dumps(&spec)).map_err(|e| {
380
- tool_runtime_error(format!(
381
- "write MCP lifecycle spec {}: {e}",
382
- spec_path.display()
383
- ))
384
- })?;
385
- if prepare_state {
386
- prepare_selected_team_state(
387
- workspace,
388
- &mut state,
389
- &team_name,
390
- &spec_workspace,
391
- &spec_path,
392
- )?;
393
- }
394
- Ok(spec_workspace)
395
- }
396
-
397
- fn selected_agents<'a>(
398
- state: &'a Value,
399
- team: Option<&str>,
400
- ) -> Option<&'a serde_json::Map<String, Value>> {
401
- team.and_then(|team| {
402
- state
403
- .get("teams")
404
- .and_then(Value::as_object)
405
- .and_then(|teams| teams.get(team))
406
- .and_then(|entry| entry.get("agents"))
407
- .and_then(Value::as_object)
408
- })
409
- .or_else(|| state.get("agents").and_then(Value::as_object))
410
- }
411
-
412
- fn prepare_selected_team_state(
413
- workspace: &Path,
414
- state: &mut Value,
415
- team: &str,
416
- spec_workspace: &Path,
417
- spec_path: &Path,
418
- ) -> Result<(), super::super::ToolError> {
419
- let Some(root) = state.as_object_mut() else {
420
- return Ok(());
421
- };
422
- let top_session_name = root.get("session_name").cloned();
423
- let top_leader_receiver = root.get("leader_receiver").cloned();
424
- let top_agents = root.get("agents").and_then(Value::as_object).cloned();
425
- let teams = root
426
- .entry("teams".to_string())
427
- .or_insert_with(|| Value::Object(serde_json::Map::new()));
428
- let Some(teams) = teams.as_object_mut() else {
429
- return Ok(());
430
- };
431
- let entry = teams
432
- .entry(team.to_string())
433
- .or_insert_with(|| Value::Object(serde_json::Map::new()));
434
- let Some(entry) = entry.as_object_mut() else {
435
- return Ok(());
436
- };
437
- if let Some(value) = top_session_name {
438
- entry.entry("session_name".to_string()).or_insert(value);
439
- }
440
- if let Some(value) = top_leader_receiver {
441
- entry.entry("leader_receiver".to_string()).or_insert(value);
442
- }
443
- entry.insert(
444
- "team_dir".to_string(),
445
- Value::String(spec_workspace.to_string_lossy().to_string()),
446
- );
447
- entry.insert(
448
- "spec_path".to_string(),
449
- Value::String(spec_path.to_string_lossy().to_string()),
450
- );
451
- if let Some(top_agents) = top_agents {
452
- if let Some(team_agents) = entry.get_mut("agents").and_then(Value::as_object_mut) {
453
- for (agent_id, top_agent) in top_agents {
454
- let Some(team_agent) = team_agents
455
- .get_mut(&agent_id)
456
- .and_then(Value::as_object_mut)
457
- else {
458
- continue;
459
- };
460
- let Some(top_agent) = top_agent.as_object() else {
461
- continue;
462
- };
463
- for (key, value) in top_agent {
464
- team_agent
465
- .entry(key.clone())
466
- .or_insert_with(|| value.clone());
467
- }
468
- }
469
- }
470
- }
471
- crate::state::repository::StateRepository::new(workspace)
472
- .save(
473
- crate::state::repository::StateWriteIntent::McpLifecycleAgentOps {
474
- team_key: Some(team),
475
- },
476
- state,
477
- )
478
- .map_err(|e| {
479
- tool_runtime_error(format!(
480
- "save MCP lifecycle scoped state {}: {e}",
481
- workspace.display()
482
- ))
483
- })
484
- }