@team-agent/installer 0.5.101 → 0.5.102

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 (42) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/tests/main_preserved.rs +0 -74
  4. package/crates/team-agent/src/compiler/tests.rs +20 -23
  5. package/crates/team-agent/src/compiler.rs +49 -65
  6. package/crates/team-agent/src/coordinator/tests/s3_bounded.rs +16 -48
  7. package/crates/team-agent/src/leader/start.rs +1 -14
  8. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -1
  9. package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
  10. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +0 -28
  11. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +11 -9
  12. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +8 -401
  13. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +2 -2
  14. package/crates/team-agent/src/lifecycle/worker_command_context.rs +22 -8
  15. package/crates/team-agent/src/mcp_server/helpers.rs +0 -16
  16. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +2 -7
  17. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -127
  18. package/crates/team-agent/src/mcp_server/mod.rs +7 -12
  19. package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -132
  20. package/crates/team-agent/src/mcp_server/tests/send.rs +1 -93
  21. package/crates/team-agent/src/mcp_server/tests/tools.rs +0 -61
  22. package/crates/team-agent/src/mcp_server/tests/wire.rs +6 -51
  23. package/crates/team-agent/src/mcp_server/tests.rs +4 -200
  24. package/crates/team-agent/src/mcp_server/tools.rs +5 -396
  25. package/crates/team-agent/src/mcp_server/types.rs +3 -36
  26. package/crates/team-agent/src/mcp_server/wire.rs +2 -223
  27. package/crates/team-agent/src/messaging/leader_receiver.rs +1 -1
  28. package/crates/team-agent/src/messaging/mod.rs +2 -2
  29. package/crates/team-agent/src/messaging/scheduler.rs +2 -205
  30. package/crates/team-agent/src/messaging/tests/basic.rs +0 -31
  31. package/crates/team-agent/src/messaging/tests/main_preserved.rs +0 -73
  32. package/crates/team-agent/src/messaging/tests/runtime.rs +0 -26
  33. package/crates/team-agent/src/messaging/types.rs +0 -20
  34. package/crates/team-agent/src/model/spec.rs +14 -18
  35. package/crates/team-agent/src/state/repository/tests.rs +1 -16
  36. package/crates/team-agent/src/state/repository.rs +1 -33
  37. package/package.json +4 -4
  38. package/skills/team-agent/SKILL.md +5 -5
  39. package/skills/team-agent/command-coverage.json +7 -0
  40. package/skills/team-agent/references/team-agent-operator.md +24 -116
  41. package/skills/team-agent/references/team-in-team.md +4 -19
  42. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +0 -484
@@ -9,7 +9,7 @@ This reference is for explicitly requested nested Team Agent teams. Use it only
9
9
  - Never edit, delete, or reuse files under the parent team's `.team/current`.
10
10
  - Never shut down a team just because `.team/current` is already occupied. In a nested setup, that is probably the parent team; shutting it down can kill the main node.
11
11
  - A child team needs its own workspace directory. The role-doc directory is not the child workspace.
12
- - MCP tools are team-scoped. A worker must not try to widen `team_orchestrator.send_message` to another team.
12
+ - The MCP surface is exactly `send_message`, `report_result`, and optional `get_team_status`; it is team-scoped. A worker must not try to widen `send_message` to another team. Lifecycle management uses the authorized native CLI.
13
13
 
14
14
  ## Child Workspace
15
15
 
@@ -34,29 +34,14 @@ Then create the child team's files inside the child workspace:
34
34
 
35
35
  ```bash
36
36
  cat > .team/current/TEAM.md <<'EOF'
37
- ---
38
- name: child-review-team
39
- objective: Run a bounded child-team task and report to the parent worker.
40
- dangerous_auto_approve: false
41
- fast: false
42
- ---
43
-
44
- Child team config only.
37
+ A bounded child team for reviewing the requested files and reporting findings.
45
38
  EOF
46
39
 
47
40
  cat > .team/current/agents/reviewer.md <<'EOF'
48
- ---
49
- name: reviewer
50
- role: Independent Reviewer
51
- provider: codex
52
- tools:
53
- - fs_read
54
- - fs_list
55
- - mcp_team
56
- ---
41
+ # Independent Reviewer
57
42
 
58
43
  Review only the files and instructions provided by the child-team leader.
59
- Report findings with team_orchestrator.report_result exactly once.
44
+ Use `send_message` for progress and report findings with `report_result` exactly once.
60
45
  EOF
61
46
 
62
47
  team-agent quick-start .team/current
@@ -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
- }