@team-agent/installer 0.5.100 → 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 (63) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/diagnose.rs +29 -7
  4. package/crates/team-agent/src/cli/mod.rs +24 -8
  5. package/crates/team-agent/src/cli/tests/main_preserved.rs +0 -74
  6. package/crates/team-agent/src/compiler/tests.rs +20 -23
  7. package/crates/team-agent/src/compiler.rs +52 -65
  8. package/crates/team-agent/src/coordinator/tests/s3_bounded.rs +16 -48
  9. package/crates/team-agent/src/leader/start.rs +1 -14
  10. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +211 -16
  11. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +1 -0
  12. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +15 -5
  13. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +89 -6
  14. package/crates/team-agent/src/lifecycle/launch/role_source.rs +168 -32
  15. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -1
  16. package/crates/team-agent/src/lifecycle/launch.rs +11 -3
  17. package/crates/team-agent/src/lifecycle/profile_launch.rs +23 -8
  18. package/crates/team-agent/src/lifecycle/restart/agent.rs +2 -2
  19. package/crates/team-agent/src/lifecycle/restart/avatar.rs +1806 -0
  20. package/crates/team-agent/src/lifecycle/restart/common.rs +115 -2
  21. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +8 -2
  22. package/crates/team-agent/src/lifecycle/restart/selection.rs +14 -2
  23. package/crates/team-agent/src/lifecycle/restart.rs +11 -4
  24. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +0 -28
  25. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +11 -9
  26. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +8 -401
  27. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +2 -2
  28. package/crates/team-agent/src/lifecycle/tests/pi_fork_session_inheritance_red.rs +1398 -0
  29. package/crates/team-agent/src/lifecycle/tests.rs +1 -0
  30. package/crates/team-agent/src/lifecycle/types.rs +10 -2
  31. package/crates/team-agent/src/lifecycle/worker_command_context.rs +22 -8
  32. package/crates/team-agent/src/mcp_server/helpers.rs +0 -16
  33. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +2 -7
  34. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -127
  35. package/crates/team-agent/src/mcp_server/mod.rs +7 -12
  36. package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -132
  37. package/crates/team-agent/src/mcp_server/tests/send.rs +1 -93
  38. package/crates/team-agent/src/mcp_server/tests/tools.rs +0 -61
  39. package/crates/team-agent/src/mcp_server/tests/wire.rs +6 -51
  40. package/crates/team-agent/src/mcp_server/tests.rs +4 -200
  41. package/crates/team-agent/src/mcp_server/tools.rs +5 -396
  42. package/crates/team-agent/src/mcp_server/types.rs +3 -36
  43. package/crates/team-agent/src/mcp_server/wire.rs +2 -223
  44. package/crates/team-agent/src/messaging/leader_receiver.rs +1 -1
  45. package/crates/team-agent/src/messaging/mod.rs +2 -2
  46. package/crates/team-agent/src/messaging/scheduler.rs +2 -205
  47. package/crates/team-agent/src/messaging/tests/basic.rs +0 -31
  48. package/crates/team-agent/src/messaging/tests/main_preserved.rs +0 -73
  49. package/crates/team-agent/src/messaging/tests/runtime.rs +0 -26
  50. package/crates/team-agent/src/messaging/types.rs +0 -20
  51. package/crates/team-agent/src/model/spec.rs +15 -18
  52. package/crates/team-agent/src/provider/types.rs +2 -0
  53. package/crates/team-agent/src/state/persist.rs +58 -2
  54. package/crates/team-agent/src/state/projection.rs +5 -1
  55. package/crates/team-agent/src/state/repository/tests.rs +1 -16
  56. package/crates/team-agent/src/state/repository.rs +126 -37
  57. package/crates/team-agent/src/transport/test_support.rs +23 -3
  58. package/package.json +4 -4
  59. package/skills/team-agent/SKILL.md +18 -47
  60. package/skills/team-agent/command-coverage.json +7 -0
  61. package/skills/team-agent/references/team-agent-operator.md +28 -118
  62. package/skills/team-agent/references/team-in-team.md +4 -19
  63. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +0 -484
@@ -290,67 +290,6 @@
290
290
  );
291
291
  }
292
292
 
293
- // ════════════════════════════════════════════════════════════════════════
294
- // CONTROL-PLANE: request_human creates a requires_ack leader message → needs_human
295
- // (tools.py:342-346). sender = explicit > env > "unknown" (never leader).
296
- //
297
- // Post-#230 N31/N32 funnel (cr-approved I-3): request_human routes through the
298
- // shared leader-delivery primitive (`send_to_leader_receiver`) instead of doing a
299
- // raw `store.create_message` bypass. Return shape from the caller's perspective is
300
- // unchanged: `status="needs_human"` + a populated `message_id`. With no leader
301
- // pane bound in this fixture, the primitive's I-4 rebind_required path STILL
302
- // persists the message row and returns its `message_id` — audit + rebind replay
303
- // both depend on it.
304
- // ════════════════════════════════════════════════════════════════════════
305
- #[test]
306
- fn request_human_returns_needs_human_with_message_id() {
307
- let tools = TeamOrchestratorTools::with_identity(
308
- &unique_ws("request-human"),
309
- Some(AgentId::new("worker-3")),
310
- Some(TeamKey::new("teamA")),
311
- );
312
- let ok = tools.request_human("need approval", Some("task-1"), None)
313
- .expect("request_human ok");
314
- let v = serde_json::to_value(&ok).unwrap();
315
- assert_eq!(v.get("status"), Some(&json!("needs_human")));
316
- assert!(v.get("message_id").and_then(Value::as_str).is_some(),
317
- "request_human must return the created leader message_id (persisted for rebind audit even on I-4 rebind_required)");
318
- }
319
-
320
- // ════════════════════════════════════════════════════════════════════════
321
- // CONTROL-PLANE: update_state appends a note + returns state_file (tools.py:316-325)
322
- // ════════════════════════════════════════════════════════════════════════
323
- #[test]
324
- fn update_state_returns_ok_and_state_file_path() {
325
- let ws = seed_state_ws(
326
- "update-state",
327
- &json!({
328
- "active_team_key": "teamA",
329
- "team_key": "teamA",
330
- "session_name": "teamA",
331
- "status": "alive",
332
- "teams": {
333
- "teamA": {
334
- "team_key": "teamA",
335
- "session_name": "teamA",
336
- "status": "alive",
337
- "agents": {}
338
- }
339
- }
340
- }),
341
- );
342
- let tools = TeamOrchestratorTools::with_identity(
343
- &ws,
344
- Some(AgentId::new("leader")),
345
- Some(TeamKey::new("teamA")),
346
- );
347
- let ok = tools.update_state("checkpoint note").expect("update_state ok");
348
- let v = serde_json::to_value(&ok).unwrap();
349
- assert_eq!(v.get("ok"), Some(&json!(true)));
350
- assert!(v.get("state_file").and_then(Value::as_str).is_some(),
351
- "update_state returns the rewritten team_state.md path");
352
- }
353
-
354
293
  // ════════════════════════════════════════════════════════════════════════
355
294
  // RpcId / RpcResponse byte-stability — null id echoed, error frame shape.
356
295
  // ════════════════════════════════════════════════════════════════════════
@@ -1,19 +1,9 @@
1
1
  #[test]
2
2
  fn mcp_tool_wire_names_and_parse_roundtrip() {
3
3
  let names = [
4
- (McpTool::AssignTask, "assign_task"),
5
4
  (McpTool::SendMessage, "send_message"),
6
5
  (McpTool::ReportResult, "report_result"),
7
- (McpTool::UpdateState, "update_state"),
8
6
  (McpTool::GetTeamStatus, "get_team_status"),
9
- (McpTool::StopAgent, "stop_agent"),
10
- (McpTool::ResetAgent, "reset_agent"),
11
- (McpTool::AddAgent, "add_agent"),
12
- (McpTool::CloneAgent, "clone_agent"),
13
- (McpTool::ForkAgent, "fork_agent"),
14
- (McpTool::RequestHuman, "request_human"),
15
- (McpTool::StuckList, "stuck_list"),
16
- (McpTool::StuckCancel, "stuck_cancel"),
17
7
  ];
18
8
  for (tool, name) in names {
19
9
  assert_eq!(tool.wire_name(), name);
@@ -45,27 +35,13 @@ fn rpc_method_classify() {
45
35
  // tools_contract — TOOLS wire list, exact names+order
46
36
  // ════════════════════════════════════════════════════════════════════════
47
37
  #[test]
48
- fn tools_contract_has_thirteen_tools_in_order() {
38
+ fn tools_contract_has_three_tools_in_order() {
49
39
  let tools = tools_contract();
50
- assert_eq!(tools.len(), 13);
40
+ assert_eq!(tools.len(), 3);
51
41
  let got: Vec<&str> = tools.iter().map(|t| t["name"].as_str().unwrap()).collect();
52
42
  assert_eq!(
53
43
  got,
54
- vec![
55
- "assign_task",
56
- "send_message",
57
- "report_result",
58
- "update_state",
59
- "get_team_status",
60
- "stop_agent",
61
- "reset_agent",
62
- "add_agent",
63
- "clone_agent",
64
- "fork_agent",
65
- "request_human",
66
- "stuck_list",
67
- "stuck_cancel",
68
- ]
44
+ vec!["send_message", "report_result", "get_team_status"]
69
45
  );
70
46
  // each carries description + inputSchema
71
47
  for t in &tools {
@@ -89,32 +65,11 @@ fn tools_contract_has_thirteen_tools_in_order() {
89
65
  "{internal} is framework-owned, not caller-supplied"
90
66
  );
91
67
  }
92
- let clone = tools
68
+ let status = tools
93
69
  .iter()
94
- .find(|tool| tool["name"] == json!("clone_agent"))
70
+ .find(|tool| tool["name"] == json!("get_team_status"))
95
71
  .unwrap();
96
- assert_eq!(
97
- clone["description"],
98
- json!("Clone a worker role into a fresh provider session.")
99
- );
100
- assert_eq!(clone["inputSchema"]["additionalProperties"], json!(false));
101
- assert_eq!(
102
- clone["inputSchema"]["required"],
103
- json!(["source_agent_id", "as_agent_id"])
104
- );
105
- assert_eq!(
106
- clone["inputSchema"]["properties"]
107
- .as_object()
108
- .unwrap()
109
- .keys()
110
- .cloned()
111
- .collect::<BTreeSet<_>>(),
112
- BTreeSet::from([
113
- "as_agent_id".to_string(),
114
- "label".to_string(),
115
- "source_agent_id".to_string(),
116
- ])
117
- );
72
+ assert_eq!(status["description"], json!("Return machine-readable team status."));
118
73
  }
119
74
 
120
75
  #[test]
@@ -1,25 +1,8 @@
1
- //! ---
2
- //! purpose: MCP no-compaction contract test with fixture-owned workspace and path provenance
3
- //! contract:
4
- //! provides:
5
- //! - name: mcp_state_fixture
6
- //! what: supplies explicit process-owned workspace and state-file paths without mutating process-global environment
7
- //! - name: mcp_state_path_provenance
8
- //! what: records raw/canonical workspace, resolved state path, and filesystem ownership facts
9
- //! depends:
10
- //! - crate::mcp_server::lifecycle_tools::state_status
11
- //! - crate::state::selector
12
- //! boundary:
13
- //! - the owned no-compaction test writes only within its fixture root
14
- //! - this suite does not alter persistence semantics
15
- //! maturity: wired
16
- //! ---
17
- //! step 14a · mcp_server::tests — WAVE-2 RED contracts (Python v0.2.11 golden).
1
+ //! MCP server unit-test fixtures and focused stdio/wire contract tests.
18
2
  #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
19
3
 
20
4
  use super::*;
21
5
  use serde_json::json;
22
- use sha2::{Digest, Sha256};
23
6
  use std::path::{Path, PathBuf};
24
7
 
25
8
  // ── helpers ──────────────────────────────────────────────────────────────
@@ -35,11 +18,9 @@ fn keys(v: &Value) -> Vec<String> {
35
18
  v.as_object().unwrap().keys().cloned().collect()
36
19
  }
37
20
 
38
- /// A UNIQUE throwaway workspace dir per test (mirrors the state/coordinator idiom):
39
- /// tests that open the db (MessageStore) or write the filesystem MUST NOT share
40
- /// `/tmp/ws`, or they flake under parallel cargo (sqlite "database is locked" / NotFound).
41
- /// Pure-function / dispatch-shape tests that never touch fs/db keep a dummy fixed path.
42
- fn unique_ws(tag: &str) -> std::path::PathBuf {
21
+ /// A UNIQUE throwaway workspace dir per test. Filesystem and database tests
22
+ /// must not share `/tmp/ws` under parallel cargo.
23
+ fn unique_ws(tag: &str) -> PathBuf {
43
24
  use std::io::ErrorKind;
44
25
  use std::sync::atomic::{AtomicU64, Ordering};
45
26
  static N: AtomicU64 = AtomicU64::new(0);
@@ -54,183 +35,6 @@ fn unique_ws(tag: &str) -> std::path::PathBuf {
54
35
  }
55
36
  }
56
37
 
57
- static MCP_FIXTURE_SEQ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
58
-
59
- /// One process-owned root for update_state tests. All paths are passed explicitly
60
- /// to the code under test; the fixture never changes process-global environment.
61
- struct McpStateFixture {
62
- root: PathBuf,
63
- workspace: PathBuf,
64
- receipt: PathBuf,
65
- }
66
-
67
- impl McpStateFixture {
68
- fn new(_tag: &str) -> Self {
69
- let seq = MCP_FIXTURE_SEQ.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
70
- let base = if cfg!(target_os = "macos") {
71
- PathBuf::from("/private/tmp")
72
- } else {
73
- PathBuf::from("/tmp")
74
- };
75
- let raw_root = base.join(format!("ta-mcp-{}-{seq}", std::process::id()));
76
- std::fs::create_dir(&raw_root).unwrap();
77
- let root = std::fs::canonicalize(raw_root).unwrap();
78
- let workspace = root.join("workspace");
79
- let receipt = root
80
- .parent()
81
- .unwrap()
82
- .join(format!("ta-mcp-receipt-{}-{seq}.json", std::process::id()));
83
- std::fs::create_dir(&workspace).unwrap();
84
-
85
- let fixture = Self {
86
- root,
87
- workspace,
88
- receipt,
89
- };
90
- fixture.seed_spec("team_state.md");
91
- crate::state::repository::StateRepository::new(&fixture.workspace)
92
- .save(
93
- crate::state::repository::StateWriteIntent::FakeE2eSeed,
94
- &json!({
95
- "session_name": "mcp-fixture",
96
- "active_team_key": "current",
97
- "agents": {},
98
- "tasks": []
99
- }),
100
- )
101
- .unwrap();
102
- fixture
103
- }
104
-
105
- fn seed_spec(&self, state_file: &str) {
106
- std::fs::write(
107
- self.workspace.join("team.spec.yaml"),
108
- format!(
109
- "team:\n name: mcp-fixture\n objective: path contract\nagents: []\ntasks: []\ncontext:\n state_file: {state_file}\n"
110
- ),
111
- )
112
- .unwrap();
113
- }
114
-
115
- fn state_file(&self, relative: &str) -> PathBuf {
116
- self.workspace.join(relative)
117
- }
118
-
119
- fn record_provenance(&self, raw_workspace: &Path, state_file: &Path) -> PathBuf {
120
- let canonical_workspace =
121
- crate::model::paths::canonical_run_workspace(raw_workspace).unwrap();
122
- assert!(self.under_root(raw_workspace));
123
- assert!(self.under_root(&canonical_workspace));
124
- assert!(self.under_root(state_file));
125
- let team_dir = self.workspace.join(".team");
126
- let runtime_dir = team_dir.join("runtime");
127
- let runtime_state = crate::state::persist::runtime_state_path(&self.workspace);
128
- let paths = [
129
- ("root", self.root.as_path()),
130
- ("raw_workspace", raw_workspace),
131
- ("canonical_workspace", canonical_workspace.as_path()),
132
- ("team_dir", team_dir.as_path()),
133
- ("runtime_dir", runtime_dir.as_path()),
134
- ("state_parent", state_file.parent().unwrap()),
135
- ("runtime_state", runtime_state.as_path()),
136
- ];
137
- let mut facts = paths
138
- .into_iter()
139
- .map(|(name, path)| {
140
- let metadata = std::fs::symlink_metadata(path).unwrap();
141
- json!({
142
- "name": name,
143
- "path": path.display().to_string(),
144
- "uid": metadata_uid(&metadata),
145
- "mode": metadata_mode(&metadata),
146
- "device": metadata_device(&metadata)
147
- })
148
- })
149
- .collect::<Vec<_>>();
150
- if let Ok(metadata) = std::fs::symlink_metadata(state_file) {
151
- facts.push(json!({
152
- "name": "state_file",
153
- "path": state_file.display().to_string(),
154
- "uid": metadata_uid(&metadata),
155
- "mode": metadata_mode(&metadata),
156
- "device": metadata_device(&metadata)
157
- }));
158
- }
159
- let payload = json!({
160
- "raw_workspace": raw_workspace.display().to_string(),
161
- "canonical_workspace": canonical_workspace.display().to_string(),
162
- "resolved_state_file": state_file.display().to_string(),
163
- "runtime_state_path": runtime_state.display().to_string(),
164
- "facts": facts
165
- });
166
- let payload_bytes = serde_json::to_vec(&payload).unwrap();
167
- let payload_sha256 = format!("{:x}", Sha256::digest(&payload_bytes));
168
- let receipt = json!({
169
- "schema": "mcp-state-provenance-v1",
170
- "payload": payload,
171
- "payload_sha256": payload_sha256
172
- });
173
- let receipt_bytes = serde_json::to_vec_pretty(&receipt).unwrap();
174
- use std::io::Write;
175
- let mut file = std::fs::OpenOptions::new()
176
- .write(true)
177
- .create_new(true)
178
- .open(&self.receipt)
179
- .unwrap();
180
- file.write_all(&receipt_bytes).unwrap();
181
- file.sync_all().unwrap();
182
- self.receipt.clone()
183
- }
184
-
185
- fn under_root(&self, path: &Path) -> bool {
186
- let candidate = if path.exists() {
187
- std::fs::canonicalize(path).unwrap()
188
- } else {
189
- path.to_path_buf()
190
- };
191
- candidate.starts_with(&self.root)
192
- }
193
- }
194
-
195
- impl Drop for McpStateFixture {
196
- fn drop(&mut self) {
197
- let _ = std::fs::remove_dir_all(&self.root);
198
- }
199
- }
200
-
201
- #[cfg(unix)]
202
- fn metadata_uid(metadata: &std::fs::Metadata) -> u32 {
203
- use std::os::unix::fs::MetadataExt;
204
- metadata.uid()
205
- }
206
-
207
- #[cfg(not(unix))]
208
- fn metadata_uid(_: &std::fs::Metadata) -> u32 {
209
- 0
210
- }
211
-
212
- #[cfg(unix)]
213
- fn metadata_mode(metadata: &std::fs::Metadata) -> u32 {
214
- use std::os::unix::fs::MetadataExt;
215
- metadata.mode()
216
- }
217
-
218
- #[cfg(not(unix))]
219
- fn metadata_mode(_: &std::fs::Metadata) -> u32 {
220
- 0
221
- }
222
-
223
- #[cfg(unix)]
224
- fn metadata_device(metadata: &std::fs::Metadata) -> u64 {
225
- use std::os::unix::fs::MetadataExt;
226
- metadata.dev()
227
- }
228
-
229
- #[cfg(not(unix))]
230
- fn metadata_device(_: &std::fs::Metadata) -> u64 {
231
- 0
232
- }
233
-
234
38
  include!("tests/normalize.rs");
235
39
  include!("tests/wire.rs");
236
40
  include!("tests/send.rs");