@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.
- package/Cargo.lock +34 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +234 -26
- package/crates/team-agent/src/cli/diagnose.rs +144 -10
- package/crates/team-agent/src/cli/emit.rs +289 -54
- package/crates/team-agent/src/cli/leader.rs +37 -8
- package/crates/team-agent/src/cli/mod.rs +1281 -196
- package/crates/team-agent/src/cli/status_port.rs +195 -46
- package/crates/team-agent/src/cli/tests/divergence.rs +1 -2
- package/crates/team-agent/src/cli/tests/lane_c.rs +23 -13
- package/crates/team-agent/src/cli/tests/main_preserved.rs +2 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +59 -3
- package/crates/team-agent/src/cli/types.rs +18 -0
- package/crates/team-agent/src/compiler.rs +15 -5
- package/crates/team-agent/src/coordinator/health.rs +95 -17
- package/crates/team-agent/src/coordinator/mod.rs +4 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +500 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +58 -0
- package/crates/team-agent/src/coordinator/tick.rs +222 -69
- package/crates/team-agent/src/coordinator/types.rs +15 -3
- package/crates/team-agent/src/db/schema.rs +37 -2
- package/crates/team-agent/src/diagnose/comms.rs +226 -0
- package/crates/team-agent/src/diagnose/mod.rs +45 -0
- package/crates/team-agent/src/diagnose/orphans.rs +658 -0
- package/crates/team-agent/src/fake_worker.rs +146 -3
- package/crates/team-agent/src/leader/start.rs +121 -23
- package/crates/team-agent/src/leader/types.rs +44 -1
- package/crates/team-agent/src/lib.rs +3 -0
- package/crates/team-agent/src/lifecycle/display.rs +645 -47
- package/crates/team-agent/src/lifecycle/launch.rs +1061 -146
- package/crates/team-agent/src/lifecycle/mod.rs +2 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +810 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +522 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +99 -23
- package/crates/team-agent/src/lifecycle/restart/common.rs +183 -24
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +498 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +27 -7
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +19 -0
- package/crates/team-agent/src/lifecycle/restart.rs +24 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +5 -5
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +37 -7
- package/crates/team-agent/src/lifecycle/types.rs +19 -0
- package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +341 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +10 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +158 -0
- package/crates/team-agent/src/mcp_server/mod.rs +3 -74
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -1
- package/crates/team-agent/src/mcp_server/tests/send.rs +6 -5
- package/crates/team-agent/src/mcp_server/tools.rs +312 -111
- package/crates/team-agent/src/mcp_server/types.rs +6 -4
- package/crates/team-agent/src/mcp_server/wire.rs +19 -7
- package/crates/team-agent/src/message_store.rs +21 -4
- package/crates/team-agent/src/messaging/delivery.rs +470 -59
- package/crates/team-agent/src/messaging/mod.rs +9 -6
- package/crates/team-agent/src/messaging/results.rs +353 -63
- package/crates/team-agent/src/messaging/selftest.rs +199 -12
- package/crates/team-agent/src/messaging/send.rs +35 -3
- package/crates/team-agent/src/messaging/tests/runtime.rs +19 -4
- package/crates/team-agent/src/messaging/types.rs +11 -3
- package/crates/team-agent/src/os_probe.rs +119 -0
- package/crates/team-agent/src/packaging/migrate.rs +10 -2
- package/crates/team-agent/src/packaging/tests.rs +23 -0
- package/crates/team-agent/src/provider/adapter.rs +564 -63
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +1 -7
- package/crates/team-agent/src/provider/classify.rs +51 -4
- package/crates/team-agent/src/provider/helpers.rs +10 -1
- package/crates/team-agent/src/provider/startup_prompt.rs +94 -0
- package/crates/team-agent/src/provider/types.rs +47 -0
- package/crates/team-agent/src/session_capture.rs +616 -0
- package/crates/team-agent/src/state/persist.rs +170 -1
- package/crates/team-agent/src/state/projection.rs +141 -8
- package/crates/team-agent/src/state/selector.rs +5 -2
- package/crates/team-agent/src/tmux_backend.rs +161 -64
- package/crates/team-agent/src/transport/test_support.rs +9 -0
- package/crates/team-agent/src/transport/tests/wire.rs +4 -0
- package/crates/team-agent/src/transport.rs +13 -2
- 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;
|
|
35
|
-
//!
|
|
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
|
|
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
|
|
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
|
|
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"
|
|
169
|
-
|
|
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
|
-
).
|
|
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]
|