@team-agent/installer 0.5.66 → 0.5.68
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 +8 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +1 -0
- package/crates/team-agent/src/cli/adapters.rs +5 -0
- package/crates/team-agent/src/cli/diagnose.rs +82 -0
- package/crates/team-agent/src/cli/emit.rs +3 -3
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +21 -7
- package/crates/team-agent/src/cli/leaders.rs +125 -1
- package/crates/team-agent/src/cli/mod.rs +20 -0
- package/crates/team-agent/src/cli/send/presentation.rs +7 -1
- package/crates/team-agent/src/cli/spec.rs +2 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
- package/crates/team-agent/src/cli/tests/base.rs +26 -3
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
- package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
- package/crates/team-agent/src/communication_mode/mod.rs +6 -4
- package/crates/team-agent/src/compiler.rs +59 -0
- package/crates/team-agent/src/coordinator/backoff.rs +55 -0
- package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
- package/crates/team-agent/src/coordinator/health.rs +173 -0
- package/crates/team-agent/src/coordinator/mod.rs +31 -0
- package/crates/team-agent/src/coordinator/orphan.rs +31 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
- package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
- package/crates/team-agent/src/coordinator/tick.rs +133 -22
- package/crates/team-agent/src/coordinator/types.rs +49 -0
- package/crates/team-agent/src/db/message_store.rs +39 -5
- package/crates/team-agent/src/layout/worker_env.rs +20 -3
- package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
- package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
- package/crates/team-agent/src/leader/registry.rs +65 -0
- package/crates/team-agent/src/leader/start.rs +920 -63
- package/crates/team-agent/src/lifecycle/display.rs +56 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -12
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +62 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +59 -0
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
- package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -456
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
- package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +62 -2
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +139 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
- package/crates/team-agent/src/lifecycle/launch.rs +99 -29
- package/crates/team-agent/src/lifecycle/lock.rs +42 -1
- package/crates/team-agent/src/lifecycle/mod.rs +11 -0
- package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +124 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +369 -11
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
- package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
- package/crates/team-agent/src/lifecycle/restart.rs +29 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
- package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
- package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +2 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
- package/crates/team-agent/src/lifecycle/tests.rs +23 -13
- package/crates/team-agent/src/lifecycle/types.rs +61 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +85 -11
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
- package/crates/team-agent/src/mcp_server/tests.rs +196 -2
- package/crates/team-agent/src/messaging/delivery.rs +405 -40
- package/crates/team-agent/src/messaging/helpers.rs +2 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
- package/crates/team-agent/src/messaging/results.rs +4 -0
- package/crates/team-agent/src/messaging/send.rs +34 -0
- package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
- package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/types.rs +4 -1
- package/crates/team-agent/src/model/enums.rs +14 -5
- package/crates/team-agent/src/model/permissions.rs +3 -0
- package/crates/team-agent/src/os_probe.rs +73 -2
- package/crates/team-agent/src/provider/adapter.rs +231 -4
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +46 -2
- package/crates/team-agent/src/provider/classify.rs +4 -2
- package/crates/team-agent/src/provider/faults.rs +2 -1
- package/crates/team-agent/src/provider/mod.rs +11 -0
- package/crates/team-agent/src/provider/session/capture.rs +158 -20
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
- package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
- package/crates/team-agent/src/provider/session/mod.rs +18 -0
- package/crates/team-agent/src/provider/session/resume.rs +57 -0
- package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
- package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
- package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
- package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
- package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
- package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
- package/crates/team-agent/src/provider/session_scan.rs +8 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +23 -1
- package/crates/team-agent/src/state/persist.rs +301 -8
- package/crates/team-agent/src/state/repository.rs +5 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
- package/crates/team-agent/src/tmux_backend.rs +1064 -75
- package/crates/team-agent/src/transport/tests/wire.rs +28 -2
- package/crates/team-agent/src/transport.rs +248 -1
- package/npm/install.mjs +129 -73
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +33 -238
- package/skills/team-agent/command-coverage.json +31 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
- package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
|
@@ -1,6 +1,32 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 动态角色文档加一席,失败按快照回滚;force 变体先摘旧席再加
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: add_agent
|
|
6
|
+
//! what: 编译角色进 runtime spec 并起席,失败回滚 spec 与 state
|
|
7
|
+
//! - name: add_agent_force
|
|
8
|
+
//! what: 先快照并摘除同名旧席,再走正常加席,失败按快照恢复
|
|
9
|
+
//! - name: add_agent_with_transport_at_paths
|
|
10
|
+
//! what: 加席的实体实现,含 owner 门、重名拒绝、原子写 spec 与起席
|
|
11
|
+
//! depends:
|
|
12
|
+
//! - crate::lifecycle::lock
|
|
13
|
+
//! - crate::lifecycle::restart
|
|
14
|
+
//! - crate::lifecycle::restart::remove
|
|
15
|
+
//! - crate::compiler
|
|
16
|
+
//! - crate::state::selector
|
|
17
|
+
//! - crate::state::projection
|
|
18
|
+
//! - crate::state::persist
|
|
19
|
+
//! - crate::tmux_backend
|
|
20
|
+
//! boundary:
|
|
21
|
+
//! - 不拷贝外部角色文件进 team 目录,就地读取编译
|
|
22
|
+
//! - 起席一律走 restart 的 start_agent_at_paths,本文件不直接 spawn
|
|
23
|
+
//! - 回滚只恢复 spec 字节与 runtime state,不回收已 spawn 的 pane
|
|
24
|
+
//! maturity: wired
|
|
25
|
+
//! ---
|
|
1
26
|
use std::collections::{BTreeMap, BTreeSet};
|
|
2
27
|
use std::path::{Path, PathBuf};
|
|
3
28
|
use std::process::Command;
|
|
29
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
4
30
|
|
|
5
31
|
use crate::lifecycle::*;
|
|
6
32
|
use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
|
|
@@ -14,6 +40,165 @@ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest}
|
|
|
14
40
|
|
|
15
41
|
use super::*;
|
|
16
42
|
|
|
43
|
+
struct AgentReservation {
|
|
44
|
+
workspace: PathBuf,
|
|
45
|
+
team_key: String,
|
|
46
|
+
agent_id: AgentId,
|
|
47
|
+
owner: String,
|
|
48
|
+
active: bool,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
impl AgentReservation {
|
|
52
|
+
fn commit(mut self) {
|
|
53
|
+
self.active = false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
impl Drop for AgentReservation {
|
|
58
|
+
fn drop(&mut self) {
|
|
59
|
+
if !self.active {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
let request = LifecycleLockRequest {
|
|
63
|
+
workspace: &self.workspace,
|
|
64
|
+
operation: "agent-reservation-rollback",
|
|
65
|
+
team: Some(self.team_key.as_str()),
|
|
66
|
+
agent_id: Some(&self.agent_id),
|
|
67
|
+
};
|
|
68
|
+
let Ok(_lock) = acquire_agent_lifecycle_lock(request) else {
|
|
69
|
+
let _ = crate::event_log::EventLog::new(&self.workspace).write(
|
|
70
|
+
"lifecycle.clone_reservation_rollback_failed",
|
|
71
|
+
serde_json::json!({
|
|
72
|
+
"agent_id": self.agent_id.as_str(),
|
|
73
|
+
"reservation_owner": self.owner,
|
|
74
|
+
"reason": "lifecycle lock unavailable",
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
return;
|
|
78
|
+
};
|
|
79
|
+
let result =
|
|
80
|
+
release_agent_reservation(&self.workspace, &self.team_key, &self.agent_id, &self.owner);
|
|
81
|
+
let _ = crate::event_log::EventLog::new(&self.workspace).write(
|
|
82
|
+
if result.is_ok() {
|
|
83
|
+
"lifecycle.clone_reservation_rolled_back"
|
|
84
|
+
} else {
|
|
85
|
+
"lifecycle.clone_reservation_rollback_failed"
|
|
86
|
+
},
|
|
87
|
+
serde_json::json!({
|
|
88
|
+
"agent_id": self.agent_id.as_str(),
|
|
89
|
+
"reservation_owner": self.owner,
|
|
90
|
+
"result": result.as_ref().err().map(ToString::to_string),
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn reserve_agent_slot(
|
|
97
|
+
workspace: &Path,
|
|
98
|
+
team: Option<&str>,
|
|
99
|
+
agent_id: &AgentId,
|
|
100
|
+
) -> Result<AgentReservation, LifecycleError> {
|
|
101
|
+
let state = crate::state::projection::select_runtime_state(workspace, team)
|
|
102
|
+
.map_err(|error| LifecycleError::TeamSelect(error.to_string()))?;
|
|
103
|
+
let team_key = team
|
|
104
|
+
.filter(|key| !key.is_empty())
|
|
105
|
+
.map(str::to_string)
|
|
106
|
+
.or_else(|| explicit_active_team_key(&state))
|
|
107
|
+
.unwrap_or_else(|| crate::state::projection::team_state_key(&state));
|
|
108
|
+
let owner = format!(
|
|
109
|
+
"{}-{}",
|
|
110
|
+
std::process::id(),
|
|
111
|
+
SystemTime::now()
|
|
112
|
+
.duration_since(UNIX_EPOCH)
|
|
113
|
+
.map_err(|error| LifecycleError::StatePersist(error.to_string()))?
|
|
114
|
+
.as_nanos()
|
|
115
|
+
);
|
|
116
|
+
let mut latest = crate::state::projection::select_runtime_state(workspace, Some(&team_key))
|
|
117
|
+
.map_err(|error| LifecycleError::TeamSelect(error.to_string()))?;
|
|
118
|
+
ensure_owner_allowed_for_state(&latest, Some(agent_id))?;
|
|
119
|
+
if runtime_agent_exists(&latest, agent_id) {
|
|
120
|
+
return Err(LifecycleError::RequirementUnmet(format!(
|
|
121
|
+
"agent id already exists: {agent_id}"
|
|
122
|
+
)));
|
|
123
|
+
}
|
|
124
|
+
latest
|
|
125
|
+
.as_object_mut()
|
|
126
|
+
.ok_or_else(|| LifecycleError::StatePersist("runtime state root is not an object".into()))?
|
|
127
|
+
.entry("agents")
|
|
128
|
+
.or_insert_with(|| serde_json::json!({}));
|
|
129
|
+
let agents = latest
|
|
130
|
+
.get_mut("agents")
|
|
131
|
+
.and_then(serde_json::Value::as_object_mut)
|
|
132
|
+
.ok_or_else(|| {
|
|
133
|
+
LifecycleError::StatePersist("runtime state agents is not an object".into())
|
|
134
|
+
})?;
|
|
135
|
+
agents.insert(
|
|
136
|
+
agent_id.as_str().to_string(),
|
|
137
|
+
serde_json::json!({
|
|
138
|
+
"agent_id": agent_id.as_str(),
|
|
139
|
+
"status": "reserved",
|
|
140
|
+
"reservation_owner": owner,
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
save_launched_team_state_for_key(
|
|
144
|
+
workspace,
|
|
145
|
+
&latest,
|
|
146
|
+
Some(team_key.as_str()),
|
|
147
|
+
Some(agent_id.as_str()),
|
|
148
|
+
)?;
|
|
149
|
+
let _ = crate::event_log::EventLog::new(workspace).write(
|
|
150
|
+
"lifecycle.clone_reservation_acquired",
|
|
151
|
+
serde_json::json!({
|
|
152
|
+
"agent_id": agent_id.as_str(),
|
|
153
|
+
"reservation_owner": owner,
|
|
154
|
+
"team": team_key,
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
Ok(AgentReservation {
|
|
158
|
+
workspace: workspace.to_path_buf(),
|
|
159
|
+
team_key,
|
|
160
|
+
agent_id: agent_id.clone(),
|
|
161
|
+
owner,
|
|
162
|
+
active: true,
|
|
163
|
+
})
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn release_agent_reservation(
|
|
167
|
+
workspace: &Path,
|
|
168
|
+
team_key: &str,
|
|
169
|
+
agent_id: &AgentId,
|
|
170
|
+
owner: &str,
|
|
171
|
+
) -> Result<(), LifecycleError> {
|
|
172
|
+
let mut state = crate::state::projection::select_runtime_state(workspace, Some(team_key))
|
|
173
|
+
.map_err(|error| LifecycleError::TeamSelect(error.to_string()))?;
|
|
174
|
+
let matches = state
|
|
175
|
+
.get("agents")
|
|
176
|
+
.and_then(|agents| agents.get(agent_id.as_str()))
|
|
177
|
+
.and_then(|agent| agent.get("reservation_owner"))
|
|
178
|
+
.and_then(serde_json::Value::as_str)
|
|
179
|
+
== Some(owner);
|
|
180
|
+
if !matches {
|
|
181
|
+
return Ok(());
|
|
182
|
+
}
|
|
183
|
+
state
|
|
184
|
+
.get_mut("agents")
|
|
185
|
+
.and_then(serde_json::Value::as_object_mut)
|
|
186
|
+
.ok_or_else(|| {
|
|
187
|
+
LifecycleError::StatePersist("runtime state agents is not an object".into())
|
|
188
|
+
})?
|
|
189
|
+
.remove(agent_id.as_str());
|
|
190
|
+
save_launched_team_state_for_key(workspace, &state, Some(team_key), Some(agent_id.as_str()))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// ---
|
|
194
|
+
/// purpose: 加一席的默认入口,解析活跃 team、拿生命周期锁、路由到该 team 实际使用的 tmux socket
|
|
195
|
+
/// params:
|
|
196
|
+
/// role_file_path: 外部角色文档路径,就地读取不拷贝
|
|
197
|
+
/// open_display: 是否为新席开显示
|
|
198
|
+
/// returns: 新席的环境与启动模式
|
|
199
|
+
/// errors: 选不到 team 返回 TeamSelect,角色文件缺失或编译失败返回 Compile,重名返回 RequirementUnmet
|
|
200
|
+
/// contract_id: lifecycle.add_agent.entry
|
|
201
|
+
/// ---
|
|
17
202
|
/// `add_agent(workspace, agent_id, role_file_path, open_display, team)`
|
|
18
203
|
/// (`lifecycle/operations.py:143`)。动态 role doc 编译进 spec + 起 worker;失败**字节级回滚**
|
|
19
204
|
/// spec_yaml / workspace_state / **team_state.md** / role_file(Gap 15.11),每步发
|
|
@@ -55,12 +240,18 @@ pub fn add_agent(
|
|
|
55
240
|
}
|
|
56
241
|
Err(error) => return Err(LifecycleError::TeamSelect(error.to_string())),
|
|
57
242
|
};
|
|
58
|
-
let
|
|
243
|
+
let lifecycle_lock = acquire_agent_lifecycle_lock(LifecycleLockRequest {
|
|
59
244
|
workspace: &selected.run_workspace,
|
|
60
245
|
operation: "add-agent",
|
|
61
246
|
team: Some(selected.team_key.as_str()),
|
|
62
247
|
agent_id: Some(agent_id),
|
|
63
248
|
})?;
|
|
249
|
+
let reservation = reserve_agent_slot(
|
|
250
|
+
&selected.run_workspace,
|
|
251
|
+
Some(selected.team_key.as_str()),
|
|
252
|
+
agent_id,
|
|
253
|
+
)?;
|
|
254
|
+
drop(lifecycle_lock);
|
|
64
255
|
// E5 §3:compile_team 要角色定义目录(team_dir),不是 spec 落点(spec_workspace=runtime)。
|
|
65
256
|
let team_dir = selected.team_dir;
|
|
66
257
|
// **0.3.24 add-agent socket drift fix**: route to the live team's persisted
|
|
@@ -74,7 +265,7 @@ pub fn add_agent(
|
|
|
74
265
|
Some(selected.team_key.as_str()),
|
|
75
266
|
)
|
|
76
267
|
.unwrap_or_else(|_| crate::tmux_backend::TmuxBackend::for_workspace(&selected.run_workspace));
|
|
77
|
-
|
|
268
|
+
add_agent_with_transport_at_paths_reserved(
|
|
78
269
|
&selected.run_workspace,
|
|
79
270
|
&team_dir,
|
|
80
271
|
agent_id,
|
|
@@ -82,9 +273,19 @@ pub fn add_agent(
|
|
|
82
273
|
open_display,
|
|
83
274
|
Some(selected.team_key.as_str()),
|
|
84
275
|
&transport,
|
|
276
|
+
Some(reservation),
|
|
277
|
+
false,
|
|
85
278
|
)
|
|
86
279
|
}
|
|
87
280
|
|
|
281
|
+
/// ---
|
|
282
|
+
/// purpose: 强制重建一席,先快照旧席再摘除再加回
|
|
283
|
+
/// params:
|
|
284
|
+
/// force: 为假时直接退回普通 add_agent
|
|
285
|
+
/// returns: 新席的环境与启动模式
|
|
286
|
+
/// errors: 任一步失败都按快照恢复,恢复本身再出错时错误里附 rollback_errors
|
|
287
|
+
/// contract_id: lifecycle.add_agent.force_entry
|
|
288
|
+
/// ---
|
|
88
289
|
/// Reconcile a single existing/inconsistent seat, then reuse the normal add
|
|
89
290
|
/// path. The external role source is preserved by remove-agent ownership
|
|
90
291
|
/// checks, so this is a one-command force-recreate rather than a team restart.
|
|
@@ -128,6 +329,12 @@ pub fn add_agent_force(
|
|
|
128
329
|
)
|
|
129
330
|
}
|
|
130
331
|
|
|
332
|
+
/// ---
|
|
333
|
+
/// purpose: 带注入 transport 的加席入口,归一 workspace 并拿锁后转实体实现
|
|
334
|
+
/// returns: 新席的环境与启动模式
|
|
335
|
+
/// errors: 归一 workspace 失败返回 StatePersist,其余透传
|
|
336
|
+
/// contract_id: lifecycle.add_agent.entry
|
|
337
|
+
/// ---
|
|
131
338
|
/// `add_agent` with an injected transport — after the recompile+write, wires the new worker spawn
|
|
132
339
|
/// (via start_agent_with_transport) + start_coordinator (rt-host-a sweep: recompiled but never spawned).
|
|
133
340
|
pub(crate) fn add_agent_with_transport(
|
|
@@ -140,13 +347,15 @@ pub(crate) fn add_agent_with_transport(
|
|
|
140
347
|
) -> Result<AddAgentReport, LifecycleError> {
|
|
141
348
|
let run_workspace = crate::model::paths::canonical_run_workspace(workspace)
|
|
142
349
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
143
|
-
let
|
|
350
|
+
let lifecycle_lock = acquire_agent_lifecycle_lock(LifecycleLockRequest {
|
|
144
351
|
workspace: &run_workspace,
|
|
145
352
|
operation: "add-agent",
|
|
146
353
|
team,
|
|
147
354
|
agent_id: Some(agent_id),
|
|
148
355
|
})?;
|
|
149
|
-
|
|
356
|
+
let reservation = reserve_agent_slot(&run_workspace, team, agent_id)?;
|
|
357
|
+
drop(lifecycle_lock);
|
|
358
|
+
add_agent_with_transport_at_paths_reserved(
|
|
150
359
|
&run_workspace,
|
|
151
360
|
workspace,
|
|
152
361
|
agent_id,
|
|
@@ -154,9 +363,19 @@ pub(crate) fn add_agent_with_transport(
|
|
|
154
363
|
open_display,
|
|
155
364
|
team,
|
|
156
365
|
transport,
|
|
366
|
+
Some(reservation),
|
|
367
|
+
false,
|
|
157
368
|
)
|
|
158
369
|
}
|
|
159
370
|
|
|
371
|
+
/// ---
|
|
372
|
+
/// purpose: 带注入 transport 的强制重建入口
|
|
373
|
+
/// params:
|
|
374
|
+
/// force: 为假时退回普通 add_agent_with_transport
|
|
375
|
+
/// returns: 新席的环境与启动模式
|
|
376
|
+
/// errors: 归一 workspace 失败返回 StatePersist,其余透传
|
|
377
|
+
/// contract_id: lifecycle.add_agent.force_entry
|
|
378
|
+
/// ---
|
|
160
379
|
pub(crate) fn add_agent_with_transport_force(
|
|
161
380
|
workspace: &Path,
|
|
162
381
|
agent_id: &AgentId,
|
|
@@ -195,6 +414,11 @@ pub(crate) fn add_agent_with_transport_force(
|
|
|
195
414
|
)
|
|
196
415
|
}
|
|
197
416
|
|
|
417
|
+
/// ---
|
|
418
|
+
/// purpose: 已持锁状态下的强制重建,先校验替换源可用再消费旧席
|
|
419
|
+
/// returns: 新席报告;成功后还要过快照的一致性校验
|
|
420
|
+
/// errors: 角色文件不存在先行返回 Compile;摘除、加回或一致性校验失败时按快照恢复并返回错误
|
|
421
|
+
/// ---
|
|
198
422
|
pub(super) fn force_recreate_with_transport_locked(
|
|
199
423
|
run_workspace: &Path,
|
|
200
424
|
team_dir: &Path,
|
|
@@ -230,7 +454,7 @@ pub(super) fn force_recreate_with_transport_locked(
|
|
|
230
454
|
let restore_errors = snapshot.restore(team, transport);
|
|
231
455
|
return force_recreate_rollback_error(agent_id, error, restore_errors);
|
|
232
456
|
}
|
|
233
|
-
let operation =
|
|
457
|
+
let operation = add_agent_with_transport_at_paths_locked(
|
|
234
458
|
run_workspace,
|
|
235
459
|
team_dir,
|
|
236
460
|
agent_id,
|
|
@@ -256,6 +480,10 @@ pub(super) fn force_recreate_with_transport_locked(
|
|
|
256
480
|
}
|
|
257
481
|
}
|
|
258
482
|
|
|
483
|
+
/// ---
|
|
484
|
+
/// purpose: 把原始错误与回滚过程中的错误合成一个对外错误
|
|
485
|
+
/// returns: 回滚干净时原样返回原始错误;否则包成 StatePersist 并附 rollback_errors
|
|
486
|
+
/// ---
|
|
259
487
|
pub(super) fn force_recreate_rollback_error<T>(
|
|
260
488
|
agent_id: &AgentId,
|
|
261
489
|
error: LifecycleError,
|
|
@@ -271,6 +499,11 @@ pub(super) fn force_recreate_rollback_error<T>(
|
|
|
271
499
|
}
|
|
272
500
|
}
|
|
273
501
|
|
|
502
|
+
/// ---
|
|
503
|
+
/// purpose: 测试用注入点,按环境变量在 spawn 之后制造一次失败
|
|
504
|
+
/// returns: 环境变量未设或为空时直接成功
|
|
505
|
+
/// errors: 设了非空值时返回 StatePersist
|
|
506
|
+
/// ---
|
|
274
507
|
pub(super) fn maybe_fail_force_recreate_after_spawn() -> Result<(), LifecycleError> {
|
|
275
508
|
let Ok(reason) = std::env::var("TEAM_AGENT_TEST_FAIL_FORCE_RECREATE_AFTER_SPAWN") else {
|
|
276
509
|
return Ok(());
|
|
@@ -283,6 +516,14 @@ pub(super) fn maybe_fail_force_recreate_after_spawn() -> Result<(), LifecycleErr
|
|
|
283
516
|
)))
|
|
284
517
|
}
|
|
285
518
|
|
|
519
|
+
/// ---
|
|
520
|
+
/// purpose: 加席的实体实现,含 owner 门、重名拒绝、重编译 spec 原子写、席位 state upsert 与起席
|
|
521
|
+
/// params:
|
|
522
|
+
/// run_workspace: 已归一的 run workspace
|
|
523
|
+
/// team_dir: 角色定义所在目录,编译 team 用它
|
|
524
|
+
/// returns: 新席的环境与启动模式
|
|
525
|
+
/// errors: owner 门不过或重名返回 RequirementUnmet,角色文件缺失或编译不一致返回 Compile,state 与 spawn 失败先回滚再透传原错
|
|
526
|
+
/// ---
|
|
286
527
|
pub(super) fn add_agent_with_transport_at_paths(
|
|
287
528
|
run_workspace: &Path,
|
|
288
529
|
team_dir: &Path,
|
|
@@ -291,6 +532,54 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
291
532
|
open_display: bool,
|
|
292
533
|
team: Option<&str>,
|
|
293
534
|
transport: &dyn Transport,
|
|
535
|
+
) -> Result<AddAgentReport, LifecycleError> {
|
|
536
|
+
add_agent_with_transport_at_paths_reserved(
|
|
537
|
+
run_workspace,
|
|
538
|
+
team_dir,
|
|
539
|
+
agent_id,
|
|
540
|
+
role_file_path,
|
|
541
|
+
open_display,
|
|
542
|
+
team,
|
|
543
|
+
transport,
|
|
544
|
+
None,
|
|
545
|
+
false,
|
|
546
|
+
)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
#[allow(clippy::too_many_arguments)]
|
|
550
|
+
fn add_agent_with_transport_at_paths_locked(
|
|
551
|
+
run_workspace: &Path,
|
|
552
|
+
team_dir: &Path,
|
|
553
|
+
agent_id: &AgentId,
|
|
554
|
+
role_file_path: &Path,
|
|
555
|
+
open_display: bool,
|
|
556
|
+
team: Option<&str>,
|
|
557
|
+
transport: &dyn Transport,
|
|
558
|
+
) -> Result<AddAgentReport, LifecycleError> {
|
|
559
|
+
add_agent_with_transport_at_paths_reserved(
|
|
560
|
+
run_workspace,
|
|
561
|
+
team_dir,
|
|
562
|
+
agent_id,
|
|
563
|
+
role_file_path,
|
|
564
|
+
open_display,
|
|
565
|
+
team,
|
|
566
|
+
transport,
|
|
567
|
+
None,
|
|
568
|
+
true,
|
|
569
|
+
)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
#[allow(clippy::too_many_arguments)]
|
|
573
|
+
fn add_agent_with_transport_at_paths_reserved(
|
|
574
|
+
run_workspace: &Path,
|
|
575
|
+
team_dir: &Path,
|
|
576
|
+
agent_id: &AgentId,
|
|
577
|
+
role_file_path: &Path,
|
|
578
|
+
open_display: bool,
|
|
579
|
+
team: Option<&str>,
|
|
580
|
+
transport: &dyn Transport,
|
|
581
|
+
reservation: Option<AgentReservation>,
|
|
582
|
+
lifecycle_lock_held: bool,
|
|
294
583
|
) -> Result<AddAgentReport, LifecycleError> {
|
|
295
584
|
let runtime_state = crate::state::persist::load_runtime_state(run_workspace)
|
|
296
585
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
@@ -309,7 +598,15 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
309
598
|
role_file_path.display()
|
|
310
599
|
)));
|
|
311
600
|
}
|
|
312
|
-
|
|
601
|
+
let reservation_owned = reservation.as_ref().is_some_and(|reservation| {
|
|
602
|
+
owner_state
|
|
603
|
+
.get("agents")
|
|
604
|
+
.and_then(|agents| agents.get(agent_id.as_str()))
|
|
605
|
+
.and_then(|agent| agent.get("reservation_owner"))
|
|
606
|
+
.and_then(serde_json::Value::as_str)
|
|
607
|
+
== Some(reservation.owner.as_str())
|
|
608
|
+
});
|
|
609
|
+
if runtime_agent_exists(&owner_state, agent_id) && !reservation_owned {
|
|
313
610
|
return Err(LifecycleError::RequirementUnmet(format!(
|
|
314
611
|
"agent id already exists: {agent_id}"
|
|
315
612
|
)));
|
|
@@ -336,20 +633,40 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
336
633
|
compiled.id, agent_id
|
|
337
634
|
)));
|
|
338
635
|
}
|
|
339
|
-
|
|
636
|
+
let compiled_agent = compiled.agent.clone();
|
|
637
|
+
inject_agent_into_spec(&mut spec, compiled_agent.clone(), &compiled.id)?;
|
|
340
638
|
// E5 spec 迁移:重编译的 spec 原子写到 .team/runtime/<team_key>/(不落用户目录 team_dir)。
|
|
341
639
|
let spec_path = crate::model::paths::runtime_spec_path(run_workspace, &canonical_team_key);
|
|
342
640
|
// E42 (0.3.24 P0): capture pre-write bytes for atomic rollback. If anything
|
|
343
641
|
// downstream of write_spec_atomic + upsert_agent_state_from_role + spawn
|
|
344
642
|
// fails, restore the prior bytes so the canonical spec / runtime state never
|
|
345
643
|
// get a half-written row that disagrees with what remove-agent can see.
|
|
644
|
+
let reservation_lock = if lifecycle_lock_held {
|
|
645
|
+
None
|
|
646
|
+
} else {
|
|
647
|
+
Some(acquire_agent_lifecycle_lock(LifecycleLockRequest {
|
|
648
|
+
workspace: run_workspace,
|
|
649
|
+
operation: "add-agent",
|
|
650
|
+
team,
|
|
651
|
+
agent_id: Some(agent_id),
|
|
652
|
+
})?)
|
|
653
|
+
};
|
|
346
654
|
let pre_spec_text = match std::fs::read_to_string(&spec_path) {
|
|
347
655
|
Ok(text) => Some(text),
|
|
348
656
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
|
|
349
657
|
Err(e) => return Err(LifecycleError::StatePersist(format!("read spec: {e}"))),
|
|
350
658
|
};
|
|
351
|
-
|
|
352
|
-
|
|
659
|
+
// Merge against the latest runtime spec while holding only the short
|
|
660
|
+
// filesystem critical section. Role compilation happened before this lock;
|
|
661
|
+
// peer reservations therefore remain visible instead of being overwritten
|
|
662
|
+
// by a stale base-spec snapshot.
|
|
663
|
+
let mut latest_spec = pre_spec_text
|
|
664
|
+
.as_deref()
|
|
665
|
+
.and_then(|text| crate::model::yaml::loads(text).ok())
|
|
666
|
+
.unwrap_or(spec);
|
|
667
|
+
inject_agent_into_spec(&mut latest_spec, compiled_agent, &compiled.id)?;
|
|
668
|
+
write_spec_atomic(&spec_path, &latest_spec)?;
|
|
669
|
+
drop(reservation_lock);
|
|
353
670
|
let (meta, _) = crate::compiler::read_front_matter(role_file_path)
|
|
354
671
|
.map_err(|e| LifecycleError::Compile(e.to_string()))?;
|
|
355
672
|
// upsert writes status="starting" (E42) — start_agent_at_paths::mark_agent_started
|
|
@@ -366,7 +683,7 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
366
683
|
run_workspace,
|
|
367
684
|
&spec_path,
|
|
368
685
|
pre_spec_text.as_deref(),
|
|
369
|
-
|
|
686
|
+
None,
|
|
370
687
|
agent_id,
|
|
371
688
|
"state_upsert_failed",
|
|
372
689
|
);
|
|
@@ -388,7 +705,7 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
388
705
|
run_workspace,
|
|
389
706
|
&spec_path,
|
|
390
707
|
pre_spec_text.as_deref(),
|
|
391
|
-
|
|
708
|
+
None,
|
|
392
709
|
agent_id,
|
|
393
710
|
"start_agent_failed",
|
|
394
711
|
);
|
|
@@ -405,7 +722,7 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
405
722
|
run_workspace,
|
|
406
723
|
&spec_path,
|
|
407
724
|
pre_spec_text.as_deref(),
|
|
408
|
-
|
|
725
|
+
None,
|
|
409
726
|
agent_id,
|
|
410
727
|
"added_agent_paused",
|
|
411
728
|
);
|
|
@@ -414,6 +731,9 @@ pub(super) fn add_agent_with_transport_at_paths(
|
|
|
414
731
|
)));
|
|
415
732
|
}
|
|
416
733
|
};
|
|
734
|
+
if let Some(reservation) = reservation {
|
|
735
|
+
reservation.commit();
|
|
736
|
+
}
|
|
417
737
|
Ok(AddAgentReport {
|
|
418
738
|
env,
|
|
419
739
|
start_mode,
|
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 加席过程中的 state 写入、spec 注入与失败回滚
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: rollback_add_agent_atomic
|
|
6
|
+
//! what: 尽力恢复 spec 字节与 runtime state,并写一条回滚事件
|
|
7
|
+
//! - name: upsert_agent_state_from_role
|
|
8
|
+
//! what: 由角色文档 front matter 写出该席位的 state 行
|
|
9
|
+
//! - name: inject_agent_into_spec
|
|
10
|
+
//! what: 把编译出的 agent 注入 spec 的 agents 与 routing 规则
|
|
11
|
+
//! - name: runtime_agent_exists
|
|
12
|
+
//! what: 判断 state 里是否已有同 id 席位
|
|
13
|
+
//! depends:
|
|
14
|
+
//! - crate::state::repository
|
|
15
|
+
//! - crate::state::persist
|
|
16
|
+
//! - crate::lifecycle::restart::remove
|
|
17
|
+
//! - crate::event_log::EventLog
|
|
18
|
+
//! boundary:
|
|
19
|
+
//! - 回滚是尽力而为,回滚自身的错误被吞掉,只在事件里如实记录成败
|
|
20
|
+
//! - 注入 spec 不落盘,落盘由调用方原子写
|
|
21
|
+
//! - 已存在同 id 的条目不重复注入
|
|
22
|
+
//! maturity: wired
|
|
23
|
+
//! ---
|
|
1
24
|
use std::collections::{BTreeMap, BTreeSet};
|
|
2
25
|
use std::path::{Path, PathBuf};
|
|
3
26
|
use std::process::Command;
|
|
@@ -14,6 +37,14 @@ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest}
|
|
|
14
37
|
|
|
15
38
|
use super::*;
|
|
16
39
|
|
|
40
|
+
/// ---
|
|
41
|
+
/// purpose: 加席失败后恢复 spec 与 runtime state,并给新席立墓碑防止被合并回来
|
|
42
|
+
/// params:
|
|
43
|
+
/// pre_spec_text: 写之前的 spec 字节;None 表示原本没有该文件,回滚即删除
|
|
44
|
+
/// pre_runtime_state: 写之前的 runtime state;None 时改为从当前 state 里摘掉该席位
|
|
45
|
+
/// reason: 写进回滚事件的原因串
|
|
46
|
+
/// returns: 无返回值;回滚成败如实写进 add_agent.rollback 事件
|
|
47
|
+
/// ---
|
|
17
48
|
/// E42 (0.3.24 P0, double-spec deadlock): best-effort atomic rollback for a
|
|
18
49
|
/// failed add-agent. Restores the canonical spec to its pre-write bytes (or
|
|
19
50
|
/// removes the file if it didn't exist), and restores runtime state to its
|
|
@@ -110,6 +141,14 @@ pub(super) fn rollback_add_agent_atomic(
|
|
|
110
141
|
);
|
|
111
142
|
}
|
|
112
143
|
|
|
144
|
+
/// ---
|
|
145
|
+
/// purpose: 由角色文档的 front matter 写出该席位的 state 行并落盘
|
|
146
|
+
/// params:
|
|
147
|
+
/// meta: 角色文档 front matter
|
|
148
|
+
/// dynamic_role_file: 角色文件路径,记进 state 供 restart 重建 spec 用
|
|
149
|
+
/// returns: 成功返回空值,席位状态记为 starting
|
|
150
|
+
/// errors: 读或写 runtime state 失败时返回 StatePersist
|
|
151
|
+
/// ---
|
|
113
152
|
pub(super) fn upsert_agent_state_from_role(
|
|
114
153
|
workspace: &Path,
|
|
115
154
|
canonical_team_key: &str,
|
|
@@ -212,6 +251,10 @@ pub(super) fn upsert_agent_state_from_role(
|
|
|
212
251
|
)
|
|
213
252
|
}
|
|
214
253
|
|
|
254
|
+
/// ---
|
|
255
|
+
/// purpose: 判断角色文件是框架托管的还是外部的
|
|
256
|
+
/// returns: 路径落在托管目录下为 managed,否则为 external
|
|
257
|
+
/// ---
|
|
215
258
|
pub(super) fn role_source_ownership(workspace: &Path, role_file: &Path) -> &'static str {
|
|
216
259
|
let managed_root = workspace.join(".team").join("dynamic-role-files");
|
|
217
260
|
match (
|
|
@@ -223,6 +266,13 @@ pub(super) fn role_source_ownership(workspace: &Path, role_file: &Path) -> &'sta
|
|
|
223
266
|
}
|
|
224
267
|
}
|
|
225
268
|
|
|
269
|
+
/// ---
|
|
270
|
+
/// purpose: 把编译出的 agent 注入 spec 的 agents 列表并补一条同形路由规则
|
|
271
|
+
/// params:
|
|
272
|
+
/// spec: 就地改写;已有同 id 的 agent 或同目标的路由规则时不重复追加
|
|
273
|
+
/// returns: 成功返回空值,不落盘
|
|
274
|
+
/// errors: spec 不是 map 或 agents 缺失时返回 Compile
|
|
275
|
+
/// ---
|
|
226
276
|
/// E5 Bug1:把 add-agent 就地编译出的 agent 条目注入 base team spec(`agents` 列表 +
|
|
227
277
|
/// `routing.rules` 加 `route-<id>`),复刻 [`compile_team`] 的路由规则形态。不落任何文件。
|
|
228
278
|
///
|
|
@@ -279,6 +329,10 @@ pub(crate) fn inject_agent_into_spec(
|
|
|
279
329
|
Ok(())
|
|
280
330
|
}
|
|
281
331
|
|
|
332
|
+
/// ---
|
|
333
|
+
/// purpose: 判断 state 的 agents 表里是否已有该 id
|
|
334
|
+
/// returns: 存在则 true
|
|
335
|
+
/// ---
|
|
282
336
|
pub(super) fn runtime_agent_exists(state: &serde_json::Value, agent_id: &AgentId) -> bool {
|
|
283
337
|
state
|
|
284
338
|
.get("agents")
|
|
@@ -286,6 +340,10 @@ pub(super) fn runtime_agent_exists(state: &serde_json::Value, agent_id: &AgentId
|
|
|
286
340
|
.is_some_and(|agents| agents.contains_key(agent_id.as_str()))
|
|
287
341
|
}
|
|
288
342
|
|
|
343
|
+
/// ---
|
|
344
|
+
/// purpose: 取 YAML agent 节点的 id
|
|
345
|
+
/// returns: id 是字符串时返回它,否则 None
|
|
346
|
+
/// ---
|
|
289
347
|
pub(super) fn yaml_agent_id(agent: &Value) -> Option<&str> {
|
|
290
348
|
let Value::Map(pairs) = agent else {
|
|
291
349
|
return None;
|
|
@@ -299,6 +357,10 @@ pub(super) fn yaml_agent_id(agent: &Value) -> Option<&str> {
|
|
|
299
357
|
})
|
|
300
358
|
}
|
|
301
359
|
|
|
360
|
+
/// ---
|
|
361
|
+
/// purpose: 取 YAML 路由规则的 assign_to
|
|
362
|
+
/// returns: 该字段是字符串时返回它,否则 None
|
|
363
|
+
/// ---
|
|
302
364
|
pub(super) fn yaml_route_assigns_to(rule: &Value) -> Option<&str> {
|
|
303
365
|
let Value::Map(pairs) = rule else {
|
|
304
366
|
return None;
|