@team-agent/installer 0.3.1 → 0.3.3

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 (79) hide show
  1. package/Cargo.lock +34 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +1 -1
  4. package/crates/team-agent/src/cli/adapters.rs +234 -26
  5. package/crates/team-agent/src/cli/diagnose.rs +144 -10
  6. package/crates/team-agent/src/cli/emit.rs +289 -54
  7. package/crates/team-agent/src/cli/leader.rs +37 -8
  8. package/crates/team-agent/src/cli/mod.rs +1281 -196
  9. package/crates/team-agent/src/cli/status_port.rs +195 -46
  10. package/crates/team-agent/src/cli/tests/divergence.rs +1 -2
  11. package/crates/team-agent/src/cli/tests/lane_c.rs +23 -13
  12. package/crates/team-agent/src/cli/tests/main_preserved.rs +2 -0
  13. package/crates/team-agent/src/cli/tests/run_delegation.rs +59 -3
  14. package/crates/team-agent/src/cli/types.rs +18 -0
  15. package/crates/team-agent/src/compiler.rs +15 -5
  16. package/crates/team-agent/src/coordinator/health.rs +95 -17
  17. package/crates/team-agent/src/coordinator/mod.rs +4 -0
  18. package/crates/team-agent/src/coordinator/runtime_detectors.rs +500 -0
  19. package/crates/team-agent/src/coordinator/runtime_observation.rs +58 -0
  20. package/crates/team-agent/src/coordinator/tick.rs +222 -69
  21. package/crates/team-agent/src/coordinator/types.rs +15 -3
  22. package/crates/team-agent/src/db/schema.rs +37 -2
  23. package/crates/team-agent/src/diagnose/comms.rs +226 -0
  24. package/crates/team-agent/src/diagnose/mod.rs +45 -0
  25. package/crates/team-agent/src/diagnose/orphans.rs +658 -0
  26. package/crates/team-agent/src/fake_worker.rs +146 -3
  27. package/crates/team-agent/src/leader/start.rs +121 -23
  28. package/crates/team-agent/src/leader/types.rs +44 -1
  29. package/crates/team-agent/src/lib.rs +3 -0
  30. package/crates/team-agent/src/lifecycle/display.rs +645 -47
  31. package/crates/team-agent/src/lifecycle/launch.rs +1061 -146
  32. package/crates/team-agent/src/lifecycle/mod.rs +2 -0
  33. package/crates/team-agent/src/lifecycle/profile_launch.rs +810 -0
  34. package/crates/team-agent/src/lifecycle/profile_smoke.rs +522 -0
  35. package/crates/team-agent/src/lifecycle/restart/agent.rs +99 -23
  36. package/crates/team-agent/src/lifecycle/restart/common.rs +183 -24
  37. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +498 -22
  38. package/crates/team-agent/src/lifecycle/restart/remove.rs +27 -7
  39. package/crates/team-agent/src/lifecycle/restart/team_state.rs +19 -0
  40. package/crates/team-agent/src/lifecycle/restart.rs +24 -1
  41. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +5 -5
  42. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +37 -7
  43. package/crates/team-agent/src/lifecycle/types.rs +19 -0
  44. package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
  45. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +341 -0
  46. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +10 -0
  47. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +158 -0
  48. package/crates/team-agent/src/mcp_server/mod.rs +3 -74
  49. package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -1
  50. package/crates/team-agent/src/mcp_server/tests/send.rs +6 -5
  51. package/crates/team-agent/src/mcp_server/tools.rs +312 -111
  52. package/crates/team-agent/src/mcp_server/types.rs +6 -4
  53. package/crates/team-agent/src/mcp_server/wire.rs +19 -7
  54. package/crates/team-agent/src/message_store.rs +21 -4
  55. package/crates/team-agent/src/messaging/delivery.rs +470 -59
  56. package/crates/team-agent/src/messaging/mod.rs +9 -6
  57. package/crates/team-agent/src/messaging/results.rs +353 -63
  58. package/crates/team-agent/src/messaging/selftest.rs +199 -12
  59. package/crates/team-agent/src/messaging/send.rs +35 -3
  60. package/crates/team-agent/src/messaging/tests/runtime.rs +19 -4
  61. package/crates/team-agent/src/messaging/types.rs +11 -3
  62. package/crates/team-agent/src/os_probe.rs +119 -0
  63. package/crates/team-agent/src/packaging/migrate.rs +10 -2
  64. package/crates/team-agent/src/packaging/tests.rs +23 -0
  65. package/crates/team-agent/src/provider/adapter.rs +564 -63
  66. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +1 -7
  67. package/crates/team-agent/src/provider/classify.rs +51 -4
  68. package/crates/team-agent/src/provider/helpers.rs +10 -1
  69. package/crates/team-agent/src/provider/startup_prompt.rs +94 -0
  70. package/crates/team-agent/src/provider/types.rs +47 -0
  71. package/crates/team-agent/src/session_capture.rs +616 -0
  72. package/crates/team-agent/src/state/persist.rs +170 -1
  73. package/crates/team-agent/src/state/projection.rs +141 -8
  74. package/crates/team-agent/src/state/selector.rs +5 -2
  75. package/crates/team-agent/src/tmux_backend.rs +161 -64
  76. package/crates/team-agent/src/transport/test_support.rs +9 -0
  77. package/crates/team-agent/src/transport/tests/wire.rs +4 -0
  78. package/crates/team-agent/src/transport.rs +13 -2
  79. package/package.json +4 -4
@@ -0,0 +1,158 @@
1
+ use std::path::Path;
2
+
3
+ use serde_json::Value;
4
+
5
+ use crate::model::ids::TeamKey;
6
+
7
+ use super::super::helpers::{ensure_object, object_fields, tool_runtime_error};
8
+ use super::super::{ToolOk, ToolResult};
9
+
10
+ pub(crate) fn update_state(
11
+ workspace: &Path,
12
+ owner_team: Option<&TeamKey>,
13
+ note: &str,
14
+ ) -> ToolResult {
15
+ let selected = match crate::state::selector::resolve_active_team(
16
+ workspace,
17
+ owner_team.map(TeamKey::as_str),
18
+ crate::state::selector::SelectorMode::RequireSpec,
19
+ ) {
20
+ Ok(selected) => selected,
21
+ Err(err) if is_missing_active_spec(&err) => {
22
+ return update_state_without_spec(workspace, owner_team, note);
23
+ }
24
+ Err(err) => return Err(tool_runtime_error(err)),
25
+ };
26
+ let mut state = selected.state;
27
+ ensure_object(&mut state);
28
+ append_note(&mut state, note);
29
+ crate::state::projection::save_team_scoped_state(&selected.run_workspace, &state)
30
+ .map_err(tool_runtime_error)?;
31
+ let spec_path = selected
32
+ .spec_path
33
+ .ok_or_else(|| tool_runtime_error("active team spec not found for update_state"))?;
34
+ let spec_workspace = spec_path.parent().ok_or_else(|| {
35
+ tool_runtime_error(format!("active team spec has no parent: {}", spec_path.display()))
36
+ })?;
37
+ let spec_text = std::fs::read_to_string(&spec_path).map_err(tool_runtime_error)?;
38
+ let spec = crate::model::yaml::loads(&spec_text).map_err(tool_runtime_error)?;
39
+ let path = crate::lifecycle::restart::write_team_state(spec_workspace, &spec, &state)
40
+ .map_err(tool_runtime_error)?;
41
+ Ok(update_state_ok(path))
42
+ }
43
+
44
+ fn update_state_without_spec(
45
+ workspace: &Path,
46
+ owner_team: Option<&TeamKey>,
47
+ note: &str,
48
+ ) -> ToolResult {
49
+ let selected = crate::state::selector::resolve_active_team(
50
+ workspace,
51
+ owner_team.map(TeamKey::as_str),
52
+ crate::state::selector::SelectorMode::RuntimeOnly,
53
+ )
54
+ .map_err(tool_runtime_error)?;
55
+ let mut state = selected.state;
56
+ ensure_object(&mut state);
57
+ seed_legacy_team_key(&mut state, &selected.run_workspace, &selected.team_key);
58
+ append_note(&mut state, note);
59
+ crate::state::projection::save_team_scoped_state(&selected.run_workspace, &state)
60
+ .map_err(tool_runtime_error)?;
61
+ let path = crate::lifecycle::restart::write_team_state(
62
+ &selected.run_workspace,
63
+ &crate::model::yaml::Value::Null,
64
+ &state,
65
+ )
66
+ .map_err(tool_runtime_error)?;
67
+ Ok(update_state_ok(path))
68
+ }
69
+
70
+ fn append_note(state: &mut Value, note: &str) {
71
+ if let Some(obj) = state.as_object_mut() {
72
+ let notes = obj
73
+ .entry("notes".to_string())
74
+ .or_insert_with(|| Value::Array(Vec::new()));
75
+ if !notes.is_array() {
76
+ *notes = Value::Array(Vec::new());
77
+ }
78
+ if let Some(items) = notes.as_array_mut() {
79
+ items.push(Value::String(note.to_string()));
80
+ }
81
+ }
82
+ }
83
+
84
+ fn seed_legacy_team_key(state: &mut Value, run_workspace: &Path, team_key: &str) {
85
+ if state.get("team_dir").and_then(Value::as_str).is_some()
86
+ || state.get("spec_path").and_then(Value::as_str).is_some()
87
+ || state.get("session_name").and_then(Value::as_str).is_some()
88
+ {
89
+ return;
90
+ }
91
+ if let Some(obj) = state.as_object_mut() {
92
+ obj.insert(
93
+ "team_dir".to_string(),
94
+ Value::String(
95
+ run_workspace
96
+ .join(".team")
97
+ .join(team_key)
98
+ .to_string_lossy()
99
+ .to_string(),
100
+ ),
101
+ );
102
+ }
103
+ }
104
+
105
+ fn update_state_ok(path: std::path::PathBuf) -> ToolOk {
106
+ let mut fields = serde_json::Map::new();
107
+ fields.insert("ok".to_string(), Value::Bool(true));
108
+ fields.insert(
109
+ "state_file".to_string(),
110
+ Value::String(path.to_string_lossy().to_string()),
111
+ );
112
+ ToolOk { fields }
113
+ }
114
+
115
+ fn is_missing_active_spec(err: &crate::state::StateError) -> bool {
116
+ matches!(
117
+ err,
118
+ crate::state::StateError::TeamSelect(message)
119
+ if message.starts_with("active team spec not found:")
120
+ )
121
+ }
122
+
123
+ pub(crate) fn get_team_status(
124
+ workspace: &Path,
125
+ owner_team: Option<&TeamKey>,
126
+ ) -> ToolResult {
127
+ let selected = crate::state::selector::resolve_active_team(
128
+ workspace,
129
+ owner_team.map(TeamKey::as_str),
130
+ crate::state::selector::SelectorMode::RuntimeOnly,
131
+ )
132
+ .map_err(tool_runtime_error)?;
133
+ let status = crate::cli::status_port::status_scoped(
134
+ &selected.run_workspace,
135
+ &selected.state,
136
+ Some(selected.team_key.as_str()),
137
+ true,
138
+ false,
139
+ )
140
+ .map_err(tool_runtime_error)?;
141
+ let mut fields = object_fields(status);
142
+ fields
143
+ .entry("teams".to_string())
144
+ .or_insert_with(|| selected_team_only(&selected.state, &selected.team_key));
145
+ Ok(ToolOk { fields })
146
+ }
147
+
148
+ fn selected_team_only(state: &Value, team_key: &str) -> Value {
149
+ let mut teams = serde_json::Map::new();
150
+ if let Some(team) = state
151
+ .get("teams")
152
+ .and_then(Value::as_object)
153
+ .and_then(|all| all.get(team_key))
154
+ {
155
+ teams.insert(team_key.to_string(), team.clone());
156
+ }
157
+ Value::Object(teams)
158
+ }
@@ -31,8 +31,8 @@
31
31
  //! 铁律 (card §11, Rust 绝不重蹈 Python 坑):
32
32
  //! - **scope 锚 env, 禁候选扫描** (C13-C17/bug-064/082): sender identity =
33
33
  //! spawn-time `TEAM_AGENT_ID`; scope = `TEAM_AGENT_OWNER_TEAM_ID`. `to="*"`
34
- //! defaults to the sender team; `scope="workspace"` is the only cross-team
35
- //! opt-in. A peer not in scope → typed [`ToolError`] with
34
+ //! defaults to the sender team; worker-origin RPC arguments cannot widen
35
+ //! that scope. A peer not in scope → typed [`ToolError`] with
36
36
  //! [`ToolErrorReason::PeerNotInScope`] — never leak other-team peer names.
37
37
  //! - **错误信封冗余键** (server.py:98-106): `reason == error_code` and
38
38
  //! `message == error` are byte-stable downstream contracts — preserved verbatim
@@ -83,6 +83,7 @@ use crate::state::persist::{load_runtime_state, save_runtime_state};
83
83
  use crate::messaging::{self, DeliveryOutcome, MessageTarget, SendOptions};
84
84
 
85
85
  pub mod helpers;
86
+ pub(crate) mod lifecycle_tools;
86
87
  pub mod normalize;
87
88
  pub mod tools;
88
89
  pub mod types;
@@ -107,77 +108,5 @@ pub(crate) use normalize::{
107
108
  };
108
109
  pub(crate) use wire::dispatch_tool;
109
110
 
110
- // ═══════════════════════════════════════════════════════════════════════════
111
- // CROSS-DEP PLACEHOLDERS — step 13 lifecycle / team_state surface not yet in tree.
112
- // The 13/15 sibling lanes are in flight; do NOT guess their authoritative names.
113
- // Leader reconciles these at integration.
114
- // ═══════════════════════════════════════════════════════════════════════════
115
-
116
- /// **PLACEHOLDER** — step 13 lifecycle `runtime.{stop,reset,add,fork}_agent`. The
117
- /// lifecycle lane is not yet in the tree; these tool handlers delegate to it. Minimal
118
- /// local stubs so the handler signatures compile and contracts can name the
119
- /// delegation. Leader swaps for the authoritative step-13 surface at integration.
120
- pub mod lifecycle_placeholder {
121
- use super::*;
122
-
123
- /// `runtime.stop_agent(workspace, agent_id)` (step 13).
124
- pub fn stop_agent(workspace: &Path, agent_id: &str) -> Result<Value, McpError> {
125
- let _ = workspace;
126
- Ok(serde_json::json!({"ok": true, "status": "stopped", "agent_id": agent_id}))
127
- }
128
-
129
- /// `runtime.reset_agent(workspace, agent_id, discard_session)` (step 13).
130
- pub fn reset_agent(workspace: &Path, agent_id: &str, discard_session: bool) -> Result<Value, McpError> {
131
- let _ = workspace;
132
- Ok(serde_json::json!({"ok": true, "status": "reset", "agent_id": agent_id, "discard_session": discard_session}))
133
- }
134
-
135
- /// `runtime.add_agent(workspace, new_agent_id, role_file_path)` (step 13).
136
- pub fn add_agent(workspace: &Path, new_agent_id: &str, role_file_path: &str) -> Result<Value, McpError> {
137
- let _ = workspace;
138
- Ok(serde_json::json!({"ok": true, "status": "added", "agent_id": new_agent_id, "role_file_path": role_file_path}))
139
- }
140
-
141
- /// `runtime.fork_agent(workspace, source_agent_id, as_agent_id, label)` (step 13).
142
- pub fn fork_agent(workspace: &Path, source_agent_id: &str, as_agent_id: &str, label: Option<&str>) -> Result<Value, McpError> {
143
- let _ = workspace;
144
- Ok(serde_json::json!({"ok": true, "status": "forked", "source_agent_id": source_agent_id, "agent_id": as_agent_id, "label": label}))
145
- }
146
-
147
- /// `runtime.status(workspace, as_json=true, compact=true)` (step 13 status
148
- /// projection; `tools.py:328`).
149
- pub fn runtime_status(workspace: &Path, compact: bool) -> Result<Value, McpError> {
150
- let _ = (workspace, compact);
151
- Ok(serde_json::json!({"ok": true, "status": "ok"}))
152
- }
153
-
154
- /// `state.write_team_state(workspace, spec, state)` (step 5/13 team_state.md
155
- /// rewrite; `tools.py:324`). Step 5 persist exists, but this writer is not yet
156
- /// exported; placeholder until the persist/lifecycle lane lands it.
157
- pub fn write_team_state(workspace: &Path, spec: &Value, state: &Value) -> Result<PathBuf, McpError> {
158
- let rel = spec
159
- .get("context")
160
- .and_then(|v| v.get("state_file"))
161
- .and_then(Value::as_str)
162
- .unwrap_or("team_state.md");
163
- let path = workspace.join(rel);
164
- if let Some(parent) = path.parent() {
165
- std::fs::create_dir_all(parent)?;
166
- }
167
- let mut text = String::from("# Team State\n\n## Notes\n\n");
168
- if let Some(notes) = state.get("notes").and_then(Value::as_array) {
169
- for note in notes {
170
- if let Some(note) = note.as_str() {
171
- text.push_str("- ");
172
- text.push_str(note);
173
- text.push('\n');
174
- }
175
- }
176
- }
177
- std::fs::write(&path, text)?;
178
- Ok(path)
179
- }
180
- }
181
-
182
111
  #[cfg(test)]
183
112
  mod tests;
@@ -185,5 +185,5 @@
185
185
  assert_eq!(refused["scope"], json!("team"));
186
186
  assert_eq!(refused["sender_team_id"], json!("teamA"));
187
187
  assert_eq!(refused["hint"],
188
- json!("the requested peer is not part of your team. pass scope='workspace' to address peers in other teams."));
188
+ json!("the requested peer is not part of your team; worker-origin MCP cannot widen team scope."));
189
189
  }
@@ -154,22 +154,23 @@
154
154
  assert_eq!(env.get("reason"), Some(&json!("peer_not_in_scope")));
155
155
  assert_eq!(
156
156
  env.get("hint"),
157
- Some(&json!("the requested peer is not part of your team. pass scope='workspace' to address peers in other teams."))
157
+ Some(&json!("the requested peer is not part of your team; worker-origin MCP cannot widen team scope."))
158
158
  );
159
159
  }
160
160
 
161
161
  #[test]
162
- fn refuse_cross_team_peer_allows_workspace_scope_optin() {
162
+ fn refuse_cross_team_peer_rejects_workspace_scope_override_for_worker() {
163
163
  let tools = TeamOrchestratorTools::with_identity(
164
164
  Path::new("/tmp/ws"),
165
165
  Some(AgentId::new("worker-1")),
166
166
  Some(TeamKey::new("teamA")),
167
167
  );
168
- // scope="workspace" None (allowed to proceed)
169
- assert!(tools.refuse_cross_team_peer(
168
+ // scope="workspace" is not worker consent to cross team boundaries.
169
+ let te = tools.refuse_cross_team_peer(
170
170
  &MessageTarget::Single("other-team-bob".to_string()),
171
171
  Some(Scope::Workspace),
172
- ).is_none(), "workspace scope opts in to cross-team addressing");
172
+ ).expect("workspace scope override must still be refused for worker-origin MCP");
173
+ assert_eq!(te.reason, ToolErrorReason::McpScopeRefused);
173
174
  }
174
175
 
175
176
  #[test]