@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/diagnose.rs +29 -7
- package/crates/team-agent/src/cli/mod.rs +24 -8
- package/crates/team-agent/src/cli/tests/main_preserved.rs +0 -74
- package/crates/team-agent/src/compiler/tests.rs +20 -23
- package/crates/team-agent/src/compiler.rs +52 -65
- package/crates/team-agent/src/coordinator/tests/s3_bounded.rs +16 -48
- package/crates/team-agent/src/leader/start.rs +1 -14
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +211 -16
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +15 -5
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +89 -6
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +168 -32
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch.rs +11 -3
- package/crates/team-agent/src/lifecycle/profile_launch.rs +23 -8
- package/crates/team-agent/src/lifecycle/restart/agent.rs +2 -2
- package/crates/team-agent/src/lifecycle/restart/avatar.rs +1806 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +115 -2
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +8 -2
- package/crates/team-agent/src/lifecycle/restart/selection.rs +14 -2
- package/crates/team-agent/src/lifecycle/restart.rs +11 -4
- package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +0 -28
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +11 -9
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +8 -401
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/pi_fork_session_inheritance_red.rs +1398 -0
- package/crates/team-agent/src/lifecycle/tests.rs +1 -0
- package/crates/team-agent/src/lifecycle/types.rs +10 -2
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +22 -8
- package/crates/team-agent/src/mcp_server/helpers.rs +0 -16
- package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +2 -7
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -127
- package/crates/team-agent/src/mcp_server/mod.rs +7 -12
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -132
- package/crates/team-agent/src/mcp_server/tests/send.rs +1 -93
- package/crates/team-agent/src/mcp_server/tests/tools.rs +0 -61
- package/crates/team-agent/src/mcp_server/tests/wire.rs +6 -51
- package/crates/team-agent/src/mcp_server/tests.rs +4 -200
- package/crates/team-agent/src/mcp_server/tools.rs +5 -396
- package/crates/team-agent/src/mcp_server/types.rs +3 -36
- package/crates/team-agent/src/mcp_server/wire.rs +2 -223
- package/crates/team-agent/src/messaging/leader_receiver.rs +1 -1
- package/crates/team-agent/src/messaging/mod.rs +2 -2
- package/crates/team-agent/src/messaging/scheduler.rs +2 -205
- package/crates/team-agent/src/messaging/tests/basic.rs +0 -31
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +0 -73
- package/crates/team-agent/src/messaging/tests/runtime.rs +0 -26
- package/crates/team-agent/src/messaging/types.rs +0 -20
- package/crates/team-agent/src/model/spec.rs +15 -18
- package/crates/team-agent/src/provider/types.rs +2 -0
- package/crates/team-agent/src/state/persist.rs +58 -2
- package/crates/team-agent/src/state/projection.rs +5 -1
- package/crates/team-agent/src/state/repository/tests.rs +1 -16
- package/crates/team-agent/src/state/repository.rs +126 -37
- package/crates/team-agent/src/transport/test_support.rs +23 -3
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +18 -47
- package/skills/team-agent/command-coverage.json +7 -0
- package/skills/team-agent/references/team-agent-operator.md +28 -118
- package/skills/team-agent/references/team-in-team.md +4 -19
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +0 -484
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//!
|
|
2
|
-
//! step 14a · mcp_server::tools — `TeamOrchestratorTools`, the
|
|
2
|
+
//! step 14a · mcp_server::tools — `TeamOrchestratorTools`, the three MCP handlers.
|
|
3
3
|
|
|
4
4
|
use std::path::{Path, PathBuf};
|
|
5
5
|
|
|
@@ -13,16 +13,14 @@ use crate::model::ids::{AgentId, TaskId, TeamKey};
|
|
|
13
13
|
use crate::event_log::EventLog;
|
|
14
14
|
|
|
15
15
|
// ── REUSE: step 5 state persist / projection ────────────────────────────────
|
|
16
|
-
use crate::state::persist::
|
|
17
|
-
load_runtime_state, save_runtime_state, save_runtime_state_reapplying_after_conflict,
|
|
18
|
-
};
|
|
16
|
+
use crate::state::persist::load_runtime_state;
|
|
19
17
|
|
|
20
18
|
// ── REUSE: step 11 messaging delegate surface ───────────────────────────────
|
|
21
19
|
use crate::messaging::{self, DeliveryStatus, MessageTarget, SendOptions, TrustedSender};
|
|
22
20
|
|
|
23
21
|
use super::helpers::{
|
|
24
22
|
current_reportable_message_for, delivery_outcome_value, direct_message_attribution_for,
|
|
25
|
-
ensure_object, enum_value, is_worker_recipient,
|
|
23
|
+
ensure_object, enum_value, is_worker_recipient, latest_task_for_assignee,
|
|
26
24
|
non_empty_string, object_fields, requires_ack_for_target, tool_runtime_error,
|
|
27
25
|
DirectMessageAttribution,
|
|
28
26
|
};
|
|
@@ -35,7 +33,7 @@ use super::types::{
|
|
|
35
33
|
};
|
|
36
34
|
|
|
37
35
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
38
|
-
// TeamOrchestratorTools
|
|
36
|
+
// TeamOrchestratorTools — the three typed MCP handlers.
|
|
39
37
|
// Identity/scope anchored on spawn-time env (TEAM_AGENT_ID / TEAM_AGENT_OWNER_TEAM_ID);
|
|
40
38
|
// every handler delegates to runtime/MessageStore/EventLog. These ARE the
|
|
41
39
|
// contract-callable behavioral entry fns.
|
|
@@ -183,100 +181,6 @@ impl TeamOrchestratorTools {
|
|
|
183
181
|
error
|
|
184
182
|
}
|
|
185
183
|
|
|
186
|
-
/// `assign_task` (`tools.py:84-133`): C8 Family-B task-view reconcile then deliver.
|
|
187
|
-
/// Resolves team key from owner-team env (or `active_team_key`), appends or
|
|
188
|
-
/// field-updates the task in state, then delegates delivery to
|
|
189
|
-
/// [`Self::send_message`] and compacts the result.
|
|
190
|
-
pub fn assign_task(&self, task: &Value, message: Option<&str>) -> ToolResult {
|
|
191
|
-
self.validate_rpc_scope_args("assign_task", task)?;
|
|
192
|
-
let Some(task_obj) = task.as_object() else {
|
|
193
|
-
return Err(ToolError::new(
|
|
194
|
-
ToolErrorReason::InvalidToolArguments,
|
|
195
|
-
"assign_task task must be an object",
|
|
196
|
-
"ValueError",
|
|
197
|
-
));
|
|
198
|
-
};
|
|
199
|
-
let Some(task_id) = task
|
|
200
|
-
.get("id")
|
|
201
|
-
.and_then(Value::as_str)
|
|
202
|
-
.and_then(non_empty_string)
|
|
203
|
-
else {
|
|
204
|
-
return Err(ToolError::new(
|
|
205
|
-
ToolErrorReason::InvalidToolArguments,
|
|
206
|
-
"assign_task task.id is required",
|
|
207
|
-
"ValueError",
|
|
208
|
-
));
|
|
209
|
-
};
|
|
210
|
-
let Some(assignee) = task
|
|
211
|
-
.get("assignee")
|
|
212
|
-
.and_then(Value::as_str)
|
|
213
|
-
.and_then(non_empty_string)
|
|
214
|
-
else {
|
|
215
|
-
return Err(ToolError::new(
|
|
216
|
-
ToolErrorReason::InvalidToolArguments,
|
|
217
|
-
"assign_task task.assignee is required",
|
|
218
|
-
"ValueError",
|
|
219
|
-
));
|
|
220
|
-
};
|
|
221
|
-
if let Some(route) = task.get("result_route") {
|
|
222
|
-
let Some(route) = route.as_str() else {
|
|
223
|
-
return Err(ToolError::new(
|
|
224
|
-
ToolErrorReason::InvalidToolArguments,
|
|
225
|
-
"assign_task task.result_route must be 'leader' or 'pipeline'",
|
|
226
|
-
"ValueError",
|
|
227
|
-
));
|
|
228
|
-
};
|
|
229
|
-
if crate::messaging::results::ResultRoute::parse(route).is_none() {
|
|
230
|
-
return Err(ToolError::new(
|
|
231
|
-
ToolErrorReason::InvalidToolArguments,
|
|
232
|
-
format!("assign_task task.result_route has unknown value: {route}"),
|
|
233
|
-
"ValueError",
|
|
234
|
-
));
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
let task_value = Value::Object(task_obj.clone());
|
|
239
|
-
let recovery = task_recovery_marker(&task_value);
|
|
240
|
-
let mut state = load_runtime_state(&self.workspace).map_err(tool_runtime_error)?;
|
|
241
|
-
ensure_object(&mut state);
|
|
242
|
-
let team_key = self
|
|
243
|
-
.canonical_owner_team_key()?
|
|
244
|
-
.map(|team| team.as_str().to_string())
|
|
245
|
-
.or_else(|| assignment_team_key(&state));
|
|
246
|
-
crate::state::repository::StateRepository::new(&self.workspace)
|
|
247
|
-
.commit(
|
|
248
|
-
crate::state::repository::StateWriteIntent::McpAssignTask {
|
|
249
|
-
team_key: team_key.as_deref(),
|
|
250
|
-
task_id,
|
|
251
|
-
},
|
|
252
|
-
|latest| {
|
|
253
|
-
ensure_object(latest);
|
|
254
|
-
let latest_team_key = team_key.clone().or_else(|| assignment_team_key(latest));
|
|
255
|
-
reconcile_assigned_task(latest, latest_team_key.as_deref(), &task_value);
|
|
256
|
-
},
|
|
257
|
-
)
|
|
258
|
-
.map_err(tool_runtime_error)?;
|
|
259
|
-
|
|
260
|
-
let content = assignment_message(task, message);
|
|
261
|
-
let out = self.send_message(
|
|
262
|
-
&MessageTarget::Single(assignee.to_string()),
|
|
263
|
-
&content,
|
|
264
|
-
Some(task_id),
|
|
265
|
-
None,
|
|
266
|
-
None,
|
|
267
|
-
)?;
|
|
268
|
-
let mut ok = compact_tool_result(&out.to_value())?;
|
|
269
|
-
if recovery {
|
|
270
|
-
ok.fields
|
|
271
|
-
.insert("recovery".to_string(), serde_json::json!(true));
|
|
272
|
-
ok.fields.insert(
|
|
273
|
-
"acceptance_marker".to_string(),
|
|
274
|
-
Value::String("recovery".to_string()),
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
Ok(ok)
|
|
278
|
-
}
|
|
279
|
-
|
|
280
184
|
/// `send_message` (`tools.py:135-183`): C14/C15/C17 scope resolution.
|
|
281
185
|
/// - sender = immutable `TEAM_AGENT_ID` captured when the MCP server starts.
|
|
282
186
|
/// - `requires_ack` defaults from target (`_requires_ack_for_target`).
|
|
@@ -722,207 +626,12 @@ impl TeamOrchestratorTools {
|
|
|
722
626
|
.and_then(|value| compact_tool_result(&value))
|
|
723
627
|
}
|
|
724
628
|
|
|
725
|
-
/// `
|
|
726
|
-
/// facade. S0 preserves the old placeholder behavior.
|
|
727
|
-
pub fn update_state(&self, note: &str) -> ToolResult {
|
|
728
|
-
let owner_team = self.canonical_owner_team_key()?;
|
|
729
|
-
super::lifecycle_tools::update_state(&self.workspace, owner_team.as_ref(), note)
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/// `get_team_status` (`tools.py:327-328`): delegated through the lifecycle tools
|
|
733
|
-
/// facade. S0 preserves the old placeholder behavior.
|
|
629
|
+
/// `get_team_status`: return the owner-team-scoped machine-readable status.
|
|
734
630
|
pub fn get_team_status(&self) -> ToolResult {
|
|
735
631
|
let owner_team = self.canonical_owner_team_key()?;
|
|
736
632
|
super::lifecycle_tools::get_team_status(&self.workspace, owner_team.as_ref())
|
|
737
633
|
}
|
|
738
634
|
|
|
739
|
-
/// `stop_agent` (`tools.py:330-331`): delegated through the lifecycle tools facade.
|
|
740
|
-
pub fn stop_agent(&self, agent_id: &str) -> ToolResult {
|
|
741
|
-
let owner_team = self.canonical_owner_team_key()?;
|
|
742
|
-
super::lifecycle_tools::stop_agent(&self.workspace, owner_team.as_ref(), agent_id)
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
/// `reset_agent` (`tools.py:333-334`): delegated through the lifecycle tools facade.
|
|
746
|
-
pub fn reset_agent(&self, agent_id: &str, discard_session: bool) -> ToolResult {
|
|
747
|
-
let owner_team = self.canonical_owner_team_key()?;
|
|
748
|
-
super::lifecycle_tools::reset_agent(
|
|
749
|
-
&self.workspace,
|
|
750
|
-
owner_team.as_ref(),
|
|
751
|
-
agent_id,
|
|
752
|
-
discard_session,
|
|
753
|
-
)
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
/// `add_agent` (`tools.py:336-337`): delegate to real lifecycle add-agent
|
|
757
|
-
/// under the spawn-time owner team.
|
|
758
|
-
pub fn add_agent(&self, new_agent_id: &str, role_file_path: &str) -> ToolResult {
|
|
759
|
-
let owner_team = self
|
|
760
|
-
.canonical_owner_team_key()?
|
|
761
|
-
.ok_or_else(|| self.scope_refused("add_agent requires TEAM_AGENT_OWNER_TEAM_ID"))?;
|
|
762
|
-
let role_file = Path::new(role_file_path);
|
|
763
|
-
let role_file = if role_file.is_absolute() {
|
|
764
|
-
role_file.to_path_buf()
|
|
765
|
-
} else {
|
|
766
|
-
self.workspace.join(role_file)
|
|
767
|
-
};
|
|
768
|
-
crate::lifecycle::launch::add_agent(
|
|
769
|
-
&self.workspace,
|
|
770
|
-
&AgentId::new(new_agent_id.to_string()),
|
|
771
|
-
&role_file,
|
|
772
|
-
false,
|
|
773
|
-
Some(owner_team.as_str()),
|
|
774
|
-
)
|
|
775
|
-
.map_err(tool_runtime_error)
|
|
776
|
-
.and_then(|report| {
|
|
777
|
-
compact_tool_result(&serde_json::json!({
|
|
778
|
-
"ok": true,
|
|
779
|
-
"status": "added",
|
|
780
|
-
"agent_id": new_agent_id,
|
|
781
|
-
"state_file": report.env.state_file.to_string_lossy().to_string(),
|
|
782
|
-
"coordinator_started": report.env.coordinator_started,
|
|
783
|
-
"start_mode": format!("{:?}", report.start_mode),
|
|
784
|
-
"role_file": report.role_file.to_string_lossy().to_string(),
|
|
785
|
-
}))
|
|
786
|
-
})
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
/// `fork_agent` (`tools.py:339-340`): delegated through the lifecycle tools facade.
|
|
790
|
-
pub fn fork_agent(
|
|
791
|
-
&self,
|
|
792
|
-
source_agent_id: &str,
|
|
793
|
-
as_agent_id: &str,
|
|
794
|
-
label: Option<&str>,
|
|
795
|
-
) -> ToolResult {
|
|
796
|
-
let owner_team = self.canonical_owner_team_key()?;
|
|
797
|
-
super::lifecycle_tools::fork_agent(
|
|
798
|
-
&self.workspace,
|
|
799
|
-
owner_team.as_ref(),
|
|
800
|
-
source_agent_id,
|
|
801
|
-
as_agent_id,
|
|
802
|
-
label,
|
|
803
|
-
)
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
pub fn clone_agent(
|
|
807
|
-
&self,
|
|
808
|
-
source_agent_id: &str,
|
|
809
|
-
as_agent_id: &str,
|
|
810
|
-
label: Option<&str>,
|
|
811
|
-
) -> ToolResult {
|
|
812
|
-
let owner_team = self.canonical_owner_team_key()?;
|
|
813
|
-
super::lifecycle_tools::clone_agent(
|
|
814
|
-
&self.workspace,
|
|
815
|
-
owner_team.as_ref(),
|
|
816
|
-
source_agent_id,
|
|
817
|
-
as_agent_id,
|
|
818
|
-
label,
|
|
819
|
-
)
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
/// `request_human` (`tools.py:342-346`): create a `requires_ack` leader message via
|
|
823
|
-
/// the shared leader-delivery funnel; sender = env / inferred / `"unknown"`.
|
|
824
|
-
/// Returns `{ok:true, message_id, status:"needs_human"}`.
|
|
825
|
-
pub fn request_human(
|
|
826
|
-
&self,
|
|
827
|
-
question: &str,
|
|
828
|
-
task_id: Option<&str>,
|
|
829
|
-
agent_id: Option<&str>,
|
|
830
|
-
) -> ToolResult {
|
|
831
|
-
let _owner_team = self.canonical_owner_team_key()?;
|
|
832
|
-
let explicit_sender = agent_id.and_then(non_empty_string);
|
|
833
|
-
let sender = explicit_sender
|
|
834
|
-
.or_else(|| self.agent_id.as_ref().map(AgentId::as_str))
|
|
835
|
-
.unwrap_or("unknown");
|
|
836
|
-
let event_log = EventLog::new(&self.workspace);
|
|
837
|
-
if explicit_sender.is_none() && self.agent_id.is_none() {
|
|
838
|
-
event_log
|
|
839
|
-
.write(
|
|
840
|
-
"mcp.identity_inference_failed",
|
|
841
|
-
serde_json::json!({"tool": "request_human"}),
|
|
842
|
-
)
|
|
843
|
-
.map_err(tool_runtime_error)?;
|
|
844
|
-
}
|
|
845
|
-
// #230 N31/N32 funnel: request_human is a leader-bound caller and must go through
|
|
846
|
-
// the same primitive as send_message(to=leader) / report_result / idle reminder.
|
|
847
|
-
// The legacy path was a raw store insert for recipient="leader" that
|
|
848
|
-
// bypassed the leader-delivery audit (no deliver_to_leader.submit emit, no rebind
|
|
849
|
-
// guard, no leader_notification_log dedup). funnel it now.
|
|
850
|
-
let state = crate::state::persist::load_runtime_state(&self.workspace)
|
|
851
|
-
.unwrap_or(serde_json::json!({}));
|
|
852
|
-
let task = task_id.map(|t| TaskId::new(t.to_string()));
|
|
853
|
-
let outcome = crate::messaging::send_to_leader_receiver(
|
|
854
|
-
&self.workspace,
|
|
855
|
-
&state,
|
|
856
|
-
"leader",
|
|
857
|
-
question,
|
|
858
|
-
task.as_ref(),
|
|
859
|
-
sender,
|
|
860
|
-
true,
|
|
861
|
-
None,
|
|
862
|
-
&event_log,
|
|
863
|
-
)
|
|
864
|
-
.map_err(tool_runtime_error)?;
|
|
865
|
-
let mut fields = serde_json::Map::new();
|
|
866
|
-
fields.insert("ok".to_string(), Value::Bool(outcome.ok));
|
|
867
|
-
fields.insert(
|
|
868
|
-
"message_id".to_string(),
|
|
869
|
-
outcome
|
|
870
|
-
.message_id
|
|
871
|
-
.clone()
|
|
872
|
-
.map_or(Value::Null, Value::String),
|
|
873
|
-
);
|
|
874
|
-
fields.insert(
|
|
875
|
-
"status".to_string(),
|
|
876
|
-
Value::String("needs_human".to_string()),
|
|
877
|
-
);
|
|
878
|
-
Ok(ToolOk { fields })
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
/// `stuck_list` (`tools.py:348-349`): delegate to [`messaging::stuck_list`] (the
|
|
882
|
-
/// team-scoped suppressed-alert projection).
|
|
883
|
-
pub fn stuck_list(&self) -> ToolResult {
|
|
884
|
-
let _owner_team = self.canonical_owner_team_key()?;
|
|
885
|
-
messaging::stuck_list(&self.workspace)
|
|
886
|
-
.map_err(tool_runtime_error)
|
|
887
|
-
.map(|v| ToolOk {
|
|
888
|
-
fields: object_fields(v),
|
|
889
|
-
})
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
/// `stuck_cancel` (`tools.py:351-352`): delegate to [`messaging::stuck_cancel`];
|
|
893
|
-
/// `suppressed_by` = env agent_id / `"leader"`.
|
|
894
|
-
pub fn stuck_cancel(&self, agent_id: &str, alert_type: &str) -> ToolResult {
|
|
895
|
-
let _owner_team = self.canonical_owner_team_key()?;
|
|
896
|
-
let alert = match alert_type {
|
|
897
|
-
"stuck" => Some(messaging::AlertType::Stuck),
|
|
898
|
-
"idle_fallback" => Some(messaging::AlertType::IdleFallback),
|
|
899
|
-
"cross_worker_deadlock" => Some(messaging::AlertType::CrossWorkerDeadlock),
|
|
900
|
-
"all" => None,
|
|
901
|
-
// scheduler.py:268-273 — an unknown alert_type refuses with the Python
|
|
902
|
-
// literal instead of silently widening to suppressing ALL alert families.
|
|
903
|
-
_ => {
|
|
904
|
-
return Ok(ToolOk {
|
|
905
|
-
fields: object_fields(serde_json::json!({
|
|
906
|
-
"ok": false,
|
|
907
|
-
"status": "refused",
|
|
908
|
-
"reason": "invalid_alert_type",
|
|
909
|
-
"alert_type": alert_type,
|
|
910
|
-
})),
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
};
|
|
914
|
-
let suppressed_by = self
|
|
915
|
-
.agent_id
|
|
916
|
-
.as_ref()
|
|
917
|
-
.map(AgentId::as_str)
|
|
918
|
-
.unwrap_or("leader");
|
|
919
|
-
messaging::stuck_cancel(&self.workspace, agent_id, alert, suppressed_by)
|
|
920
|
-
.map_err(tool_runtime_error)
|
|
921
|
-
.map(|v| ToolOk {
|
|
922
|
-
fields: object_fields(v),
|
|
923
|
-
})
|
|
924
|
-
}
|
|
925
|
-
|
|
926
635
|
/// `get_visible_peers` (`tools.py:226-247`): C16 scope-filtered peer list — live
|
|
927
636
|
/// agents within the spawn-time owner-team scope only; other teams and dead/stopped
|
|
928
637
|
/// agents are filtered server-side and never named.
|
|
@@ -1275,63 +984,6 @@ fn requested_scope_arg(args: &Value) -> Option<String> {
|
|
|
1275
984
|
.or_else(|| args.get("workspace").map(|_| "workspace".to_string()))
|
|
1276
985
|
}
|
|
1277
986
|
|
|
1278
|
-
fn assignment_team_key(state: &Value) -> Option<String> {
|
|
1279
|
-
state
|
|
1280
|
-
.get("active_team_key")
|
|
1281
|
-
.and_then(Value::as_str)
|
|
1282
|
-
.and_then(non_empty_string)
|
|
1283
|
-
.map(ToString::to_string)
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
fn reconcile_assigned_task(state: &mut Value, team_key: Option<&str>, task: &Value) {
|
|
1287
|
-
let mut top = state
|
|
1288
|
-
.get("tasks")
|
|
1289
|
-
.and_then(Value::as_array)
|
|
1290
|
-
.cloned()
|
|
1291
|
-
.unwrap_or_default();
|
|
1292
|
-
upsert_task_in_place(&mut top, task);
|
|
1293
|
-
if let Some(root) = state.as_object_mut() {
|
|
1294
|
-
root.insert("tasks".to_string(), Value::Array(top.clone()));
|
|
1295
|
-
}
|
|
1296
|
-
if let Some(key) = team_key {
|
|
1297
|
-
let mut team_tasks = state
|
|
1298
|
-
.get("teams")
|
|
1299
|
-
.and_then(|v| v.get(key))
|
|
1300
|
-
.and_then(|team| team.get("tasks"))
|
|
1301
|
-
.and_then(Value::as_array)
|
|
1302
|
-
.cloned()
|
|
1303
|
-
.unwrap_or_default();
|
|
1304
|
-
upsert_task_in_place(&mut team_tasks, task);
|
|
1305
|
-
write_team_tasks(state, key, team_tasks);
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
fn upsert_task_in_place(tasks: &mut Vec<Value>, task: &Value) {
|
|
1310
|
-
let Some(task_id) = task.get("id").and_then(Value::as_str) else {
|
|
1311
|
-
return;
|
|
1312
|
-
};
|
|
1313
|
-
for existing in tasks.iter_mut() {
|
|
1314
|
-
if existing.get("id").and_then(Value::as_str) == Some(task_id) {
|
|
1315
|
-
merge_object_fields(existing, task);
|
|
1316
|
-
return;
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
tasks.push(task.clone());
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
fn merge_object_fields(existing: &mut Value, incoming: &Value) {
|
|
1323
|
-
let Some(existing_obj) = existing.as_object_mut() else {
|
|
1324
|
-
*existing = incoming.clone();
|
|
1325
|
-
return;
|
|
1326
|
-
};
|
|
1327
|
-
let Some(incoming_obj) = incoming.as_object() else {
|
|
1328
|
-
return;
|
|
1329
|
-
};
|
|
1330
|
-
for (key, value) in incoming_obj {
|
|
1331
|
-
existing_obj.insert(key.clone(), value.clone());
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
987
|
fn push_report_warning(obj: &mut serde_json::Map<String, Value>, warning: Value) {
|
|
1336
988
|
let Some(code) = warning.get("code").and_then(Value::as_str) else {
|
|
1337
989
|
return;
|
|
@@ -1355,49 +1007,6 @@ fn push_report_warning(obj: &mut serde_json::Map<String, Value>, warning: Value)
|
|
|
1355
1007
|
}
|
|
1356
1008
|
}
|
|
1357
1009
|
|
|
1358
|
-
fn write_team_tasks(state: &mut Value, team_key: &str, tasks: Vec<Value>) {
|
|
1359
|
-
let Some(root) = state.as_object_mut() else {
|
|
1360
|
-
return;
|
|
1361
|
-
};
|
|
1362
|
-
let teams = root
|
|
1363
|
-
.entry("teams".to_string())
|
|
1364
|
-
.or_insert_with(|| Value::Object(serde_json::Map::new()));
|
|
1365
|
-
let Some(teams_obj) = teams.as_object_mut() else {
|
|
1366
|
-
return;
|
|
1367
|
-
};
|
|
1368
|
-
let team = teams_obj.entry(team_key.to_string()).or_insert_with(|| {
|
|
1369
|
-
let mut team = serde_json::Map::new();
|
|
1370
|
-
team.insert("tasks".to_string(), Value::Array(Vec::new()));
|
|
1371
|
-
team.insert("status".to_string(), Value::String("alive".to_string()));
|
|
1372
|
-
Value::Object(team)
|
|
1373
|
-
});
|
|
1374
|
-
let Some(team_obj) = team.as_object_mut() else {
|
|
1375
|
-
return;
|
|
1376
|
-
};
|
|
1377
|
-
team_obj.insert("tasks".to_string(), Value::Array(tasks));
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
fn assignment_message(task: &Value, explicit: Option<&str>) -> String {
|
|
1381
|
-
if let Some(message) = explicit.and_then(non_empty_string) {
|
|
1382
|
-
return message.to_string();
|
|
1383
|
-
}
|
|
1384
|
-
for key in ["description", "title"] {
|
|
1385
|
-
if let Some(text) = task
|
|
1386
|
-
.get(key)
|
|
1387
|
-
.and_then(Value::as_str)
|
|
1388
|
-
.and_then(non_empty_string)
|
|
1389
|
-
{
|
|
1390
|
-
return text.to_string();
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
json_dumps_default(task)
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
fn task_recovery_marker(task: &Value) -> bool {
|
|
1397
|
-
task.get("recovery").and_then(Value::as_bool) == Some(true)
|
|
1398
|
-
|| task.get("acceptance_marker").and_then(Value::as_str) == Some("recovery")
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
1010
|
fn scope_override_name(scope: Scope) -> Option<&'static str> {
|
|
1402
1011
|
match scope {
|
|
1403
1012
|
Scope::Team => Some("team"),
|
|
@@ -48,48 +48,25 @@ pub enum McpError {
|
|
|
48
48
|
// WIRE ENUMS (§19) —散字符串 → 穷尽 enum;契约字节级真相由这些派生。
|
|
49
49
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
50
50
|
|
|
51
|
-
/// The
|
|
52
|
-
///
|
|
53
|
-
/// `TOOLS` wire list returned by `tools/list`) MUST be derived from this enum so
|
|
54
|
-
/// name/inputSchema stay byte-identical to `contracts.py`.
|
|
51
|
+
/// The three MCP tool names. Unknown names map to [`ToolErrorReason::UnknownTool`].
|
|
52
|
+
/// [`tools_contract`] (the `tools/list` payload) is derived from this enum.
|
|
55
53
|
///
|
|
56
54
|
/// [`tools_contract`]: super::tools_contract
|
|
57
55
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
58
56
|
#[serde(rename_all = "snake_case")]
|
|
59
57
|
pub enum McpTool {
|
|
60
|
-
AssignTask,
|
|
61
58
|
SendMessage,
|
|
62
59
|
ReportResult,
|
|
63
|
-
UpdateState,
|
|
64
60
|
GetTeamStatus,
|
|
65
|
-
StopAgent,
|
|
66
|
-
ResetAgent,
|
|
67
|
-
AddAgent,
|
|
68
|
-
CloneAgent,
|
|
69
|
-
ForkAgent,
|
|
70
|
-
RequestHuman,
|
|
71
|
-
StuckList,
|
|
72
|
-
StuckCancel,
|
|
73
61
|
}
|
|
74
62
|
|
|
75
63
|
impl McpTool {
|
|
76
|
-
/// Wire name
|
|
77
|
-
/// `tools/list` and `tools/call`. Parse failure ⇒ [`ToolErrorReason::UnknownTool`].
|
|
64
|
+
/// Wire name used in `tools/list` and `tools/call`.
|
|
78
65
|
pub fn wire_name(self) -> &'static str {
|
|
79
66
|
match self {
|
|
80
|
-
McpTool::AssignTask => "assign_task",
|
|
81
67
|
McpTool::SendMessage => "send_message",
|
|
82
68
|
McpTool::ReportResult => "report_result",
|
|
83
|
-
McpTool::UpdateState => "update_state",
|
|
84
69
|
McpTool::GetTeamStatus => "get_team_status",
|
|
85
|
-
McpTool::StopAgent => "stop_agent",
|
|
86
|
-
McpTool::ResetAgent => "reset_agent",
|
|
87
|
-
McpTool::AddAgent => "add_agent",
|
|
88
|
-
McpTool::CloneAgent => "clone_agent",
|
|
89
|
-
McpTool::ForkAgent => "fork_agent",
|
|
90
|
-
McpTool::RequestHuman => "request_human",
|
|
91
|
-
McpTool::StuckList => "stuck_list",
|
|
92
|
-
McpTool::StuckCancel => "stuck_cancel",
|
|
93
70
|
}
|
|
94
71
|
}
|
|
95
72
|
|
|
@@ -97,19 +74,9 @@ impl McpTool {
|
|
|
97
74
|
/// to [`ToolErrorReason::UnknownTool`], matching `server.py:43`).
|
|
98
75
|
pub fn parse(name: &str) -> Option<Self> {
|
|
99
76
|
match name {
|
|
100
|
-
"assign_task" => Some(McpTool::AssignTask),
|
|
101
77
|
"send_message" => Some(McpTool::SendMessage),
|
|
102
78
|
"report_result" => Some(McpTool::ReportResult),
|
|
103
|
-
"update_state" => Some(McpTool::UpdateState),
|
|
104
79
|
"get_team_status" => Some(McpTool::GetTeamStatus),
|
|
105
|
-
"stop_agent" => Some(McpTool::StopAgent),
|
|
106
|
-
"reset_agent" => Some(McpTool::ResetAgent),
|
|
107
|
-
"add_agent" => Some(McpTool::AddAgent),
|
|
108
|
-
"clone_agent" => Some(McpTool::CloneAgent),
|
|
109
|
-
"fork_agent" => Some(McpTool::ForkAgent),
|
|
110
|
-
"request_human" => Some(McpTool::RequestHuman),
|
|
111
|
-
"stuck_list" => Some(McpTool::StuckList),
|
|
112
|
-
"stuck_cancel" => Some(McpTool::StuckCancel),
|
|
113
80
|
_ => None,
|
|
114
81
|
}
|
|
115
82
|
}
|