@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,7 +1,29 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: plan 的停止与状态读取
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: halt_plan
|
|
6
|
+
//! what: 读出 plan 状态并返回 Halted 结论
|
|
7
|
+
//! - name: plan_status
|
|
8
|
+
//! what: 读出 plan 的持久化状态
|
|
9
|
+
//! depends:
|
|
10
|
+
//! - crate::lifecycle::helpers
|
|
11
|
+
//! boundary:
|
|
12
|
+
//! - 只读状态文件,本文件不改写 plan 状态
|
|
13
|
+
//! - 不向席位投递任何消息
|
|
14
|
+
//! maturity: wired
|
|
15
|
+
//! ---
|
|
1
16
|
use super::*;
|
|
2
17
|
|
|
3
18
|
// ── lifecycle::orchestrator —— plan 多 stage 状态机(halt / status)──────────
|
|
4
19
|
|
|
20
|
+
/// ---
|
|
21
|
+
/// purpose: 对指定 plan 返回 Halted 结论
|
|
22
|
+
/// params:
|
|
23
|
+
/// reason: 当前实现未使用
|
|
24
|
+
/// returns: Halted,理由恒为 already_terminal;注意它不写盘,磁盘上的 status 不变
|
|
25
|
+
/// errors: 状态文件不存在或解析失败时返回 InvalidPlan
|
|
26
|
+
/// ---
|
|
5
27
|
/// `halt_plan(workspace, plan_id, reason)`(`orchestrator/__init__.py:152`)。停 plan;
|
|
6
28
|
/// 非 running → 幂等返回。
|
|
7
29
|
pub(crate) fn halt_plan(
|
|
@@ -25,6 +47,11 @@ pub(crate) fn halt_plan(
|
|
|
25
47
|
})
|
|
26
48
|
}
|
|
27
49
|
|
|
50
|
+
/// ---
|
|
51
|
+
/// purpose: 读出 plan 的持久化状态
|
|
52
|
+
/// returns: 反序列化出的 PlanState
|
|
53
|
+
/// errors: 状态文件不存在或解析失败时返回 InvalidPlan
|
|
54
|
+
/// ---
|
|
28
55
|
/// `plan_status(workspace, plan_id)`(`orchestrator/__init__.py:177`)。读 plan 持久态。
|
|
29
56
|
pub(crate) fn plan_status(workspace: &Path, plan_id: &PlanId) -> Result<PlanState, LifecycleError> {
|
|
30
57
|
let path = plan_state_path(workspace, plan_id);
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: restart 拆除 worker session 之前的身份守卫,防止误杀 leader 会话
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: SessionPreflight
|
|
6
|
+
//! what: 守卫结论,通过或指出 worker session 名其实是 leader 会话名
|
|
7
|
+
//! - name: check_session_preflight
|
|
8
|
+
//! what: 纯函数检查 state 里的 session 名字段
|
|
9
|
+
//! depends:
|
|
10
|
+
//! - crate::layout::RuntimeSessions
|
|
11
|
+
//! boundary:
|
|
12
|
+
//! - 纯计算,不做 IO 也不改 state
|
|
13
|
+
//! - 只判这一种异常,其他 session 异常不在本守卫范围
|
|
14
|
+
//! maturity: wired
|
|
15
|
+
//! ---
|
|
1
16
|
//! unit-3 (Stage 1) — restart preflight session-identity guard.
|
|
2
17
|
//!
|
|
3
18
|
//! Single check that runs BEFORE the worker-session teardown in
|
|
@@ -27,12 +42,22 @@ pub enum SessionPreflight {
|
|
|
27
42
|
}
|
|
28
43
|
|
|
29
44
|
impl SessionPreflight {
|
|
45
|
+
/// ---
|
|
46
|
+
/// purpose: 判断守卫是否放行
|
|
47
|
+
/// returns: 结论为 Ok 时为 true
|
|
48
|
+
/// ---
|
|
30
49
|
/// True when the preflight passed and the caller may proceed.
|
|
31
50
|
pub fn is_ok(&self) -> bool {
|
|
32
51
|
matches!(self, SessionPreflight::Ok)
|
|
33
52
|
}
|
|
34
53
|
}
|
|
35
54
|
|
|
55
|
+
/// ---
|
|
56
|
+
/// purpose: 检查 state 里的 session 名,决定接下来的 worker session 拆除是否安全
|
|
57
|
+
/// params:
|
|
58
|
+
/// state: runtime state
|
|
59
|
+
/// returns: 发现 worker session 名带 leader 前缀时返回拒绝结论并附名字与原因,否则 Ok
|
|
60
|
+
/// ---
|
|
36
61
|
/// Inspect `state.json` and decide whether the upcoming worker-session
|
|
37
62
|
/// kill is safe. Pure: no I/O, no mutation.
|
|
38
63
|
pub fn check_session_preflight(state: &serde_json::Value) -> SessionPreflight {
|
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 整队重建,先算 resume 计划与拒绝结论,再做破坏性拆除与重起
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: restart
|
|
6
|
+
//! what: 整队重建入口,解析 team 与它实际使用的 transport 后执行
|
|
7
|
+
//! - name: restart_with_transport_with_session_convergence_deadline
|
|
8
|
+
//! what: 重建的实体实现,含入口门、spec 重建、计划分类、四种拒绝与重起
|
|
9
|
+
//! - name: RestartPhaseTimer
|
|
10
|
+
//! what: 阶段计时器,把 restart 与 launch 的分段耗时写成事件
|
|
11
|
+
//! - name: restart_candidates
|
|
12
|
+
//! what: 列出本 workspace 可重启的团队
|
|
13
|
+
//! - name: select_restart_state
|
|
14
|
+
//! what: 按 team 参数或唯一性选出重启目标
|
|
15
|
+
//! depends:
|
|
16
|
+
//! - crate::lifecycle::lock
|
|
17
|
+
//! - crate::lifecycle::display
|
|
18
|
+
//! - crate::lifecycle::restart::selection
|
|
19
|
+
//! - crate::lifecycle::restart::preflight
|
|
20
|
+
//! - crate::lifecycle::restart::remove
|
|
21
|
+
//! - crate::state::selector
|
|
22
|
+
//! - crate::state::projection
|
|
23
|
+
//! - crate::state::persist
|
|
24
|
+
//! - crate::event_log::EventLog
|
|
25
|
+
//! boundary:
|
|
26
|
+
//! - 四种拒绝一律先于任何 teardown 产出,拒绝时现场不变
|
|
27
|
+
//! - 每个非 paused 席位必发一条 resume 决策事件
|
|
28
|
+
//! - 只有计划里不含 Resumed 时才并行起席,串行臂的会话消失检测没有并发等价物
|
|
29
|
+
//! maturity: wired
|
|
30
|
+
//! ---
|
|
1
31
|
use super::common::*;
|
|
2
32
|
use super::selection::classify_restart_plan_with_resume_validation;
|
|
3
33
|
use super::*;
|
|
@@ -17,16 +47,31 @@ pub(crate) struct RestartPhaseTimer {
|
|
|
17
47
|
}
|
|
18
48
|
|
|
19
49
|
impl RestartPhaseTimer {
|
|
50
|
+
/// ---
|
|
51
|
+
/// purpose: 起一个阶段计时器
|
|
52
|
+
/// returns: 以当前单调时钟为起点的计时器
|
|
53
|
+
/// ---
|
|
20
54
|
pub(crate) fn start() -> Self {
|
|
21
55
|
Self {
|
|
22
56
|
started_at: std::time::Instant::now(),
|
|
23
57
|
}
|
|
24
58
|
}
|
|
25
59
|
|
|
60
|
+
/// ---
|
|
61
|
+
/// purpose: 取从起点到现在的毫秒数
|
|
62
|
+
/// returns: 毫秒数,溢出时取上限值
|
|
63
|
+
/// ---
|
|
26
64
|
pub(crate) fn elapsed_ms(&self) -> u64 {
|
|
27
65
|
u64::try_from(self.started_at.elapsed().as_millis()).unwrap_or(u64::MAX)
|
|
28
66
|
}
|
|
29
67
|
|
|
68
|
+
/// ---
|
|
69
|
+
/// purpose: 写一条带阶段名与已用毫秒数的事件
|
|
70
|
+
/// params:
|
|
71
|
+
/// kind: 事件名,restart.phase 或 launch.phase
|
|
72
|
+
/// phase: 阶段名
|
|
73
|
+
/// returns: 写事件失败被吞掉,不影响主流程
|
|
74
|
+
/// ---
|
|
30
75
|
pub(crate) fn emit(&self, workspace: &Path, kind: &'static str, phase: &'static str) {
|
|
31
76
|
let event_log = crate::event_log::EventLog::new(workspace);
|
|
32
77
|
let _ = event_log.write(
|
|
@@ -41,6 +86,15 @@ impl RestartPhaseTimer {
|
|
|
41
86
|
|
|
42
87
|
// ── lifecycle::restart —— 整队 Route B resume-or-fresh 重建 ──────────────────
|
|
43
88
|
|
|
89
|
+
/// ---
|
|
90
|
+
/// purpose: 整队重建的对外入口
|
|
91
|
+
/// params:
|
|
92
|
+
/// allow_fresh: 允许把不可 resume 的席位改跑全新会话
|
|
93
|
+
/// team: 指定团队,None 时按唯一性选
|
|
94
|
+
/// returns: 重建报告,含成功与失败席位,或四种拒绝之一
|
|
95
|
+
/// errors: 选不到 team 返回 TeamSelect,路径归一失败返回 StatePersist
|
|
96
|
+
/// contract_id: lifecycle.rebuild.restart_entry
|
|
97
|
+
/// ---
|
|
44
98
|
/// `restart(workspace, allow_fresh, team)`(`restart/orchestration.py:26`)。整队重建:
|
|
45
99
|
/// **先**算 resume 决策(Route B)+ `first_send_at` 严格校验(corrupt → hard refuse),
|
|
46
100
|
/// **再**做破坏性 teardown(关显示、建 session)、起后 leader rebind、adaptive 显示重建。
|
|
@@ -53,6 +107,14 @@ pub fn restart(
|
|
|
53
107
|
restart_with_session_convergence_deadline(workspace, allow_fresh, team, None)
|
|
54
108
|
}
|
|
55
109
|
|
|
110
|
+
/// ---
|
|
111
|
+
/// purpose: 带会话收敛等待上限的整队重建入口
|
|
112
|
+
/// params:
|
|
113
|
+
/// session_converge_deadline_ms: 等待会话收敛的上限,None 用默认
|
|
114
|
+
/// returns: 重建报告
|
|
115
|
+
/// errors: 同 restart
|
|
116
|
+
/// contract_id: lifecycle.rebuild.restart_entry
|
|
117
|
+
/// ---
|
|
56
118
|
pub fn restart_with_session_convergence_deadline(
|
|
57
119
|
workspace: &Path,
|
|
58
120
|
allow_fresh: bool,
|
|
@@ -106,6 +168,12 @@ fn resolve_restart_context(
|
|
|
106
168
|
})
|
|
107
169
|
}
|
|
108
170
|
|
|
171
|
+
/// ---
|
|
172
|
+
/// purpose: 带注入 transport 的整队重建入口
|
|
173
|
+
/// returns: 重建报告
|
|
174
|
+
/// errors: 透传实体实现的错误
|
|
175
|
+
/// contract_id: lifecycle.rebuild.restart_entry
|
|
176
|
+
/// ---
|
|
109
177
|
/// `restart` with an injected transport (tests: recording mock; prod: real TmuxBackend). The Route-B
|
|
110
178
|
/// resume/fresh worker spawn + start_coordinator are wired here over `transport`. (rt-host-a sweep:
|
|
111
179
|
/// was a stub returning RequirementUnmet at the spawn boundary — never spawned/resumed/started coordinator.)
|
|
@@ -118,6 +186,13 @@ pub(crate) fn restart_with_transport(
|
|
|
118
186
|
restart_with_transport_with_readiness_deadline(workspace, allow_fresh, team, transport, None)
|
|
119
187
|
}
|
|
120
188
|
|
|
189
|
+
/// ---
|
|
190
|
+
/// purpose: 带就绪等待上限的重建入口
|
|
191
|
+
/// params:
|
|
192
|
+
/// readiness_deadline_ms: 等待就绪的上限
|
|
193
|
+
/// returns: 重建报告;注意本入口把 RefusedResumeNotReady 重映射成 RefusedResumeAtomicity 并把原因记为会话捕获未收敛,两种拒绝在此入口不可分辨
|
|
194
|
+
/// errors: 透传实体实现的错误
|
|
195
|
+
/// ---
|
|
121
196
|
pub(crate) fn restart_with_transport_with_readiness_deadline(
|
|
122
197
|
workspace: &Path,
|
|
123
198
|
allow_fresh: bool,
|
|
@@ -158,6 +233,14 @@ pub(crate) fn restart_with_transport_with_readiness_deadline(
|
|
|
158
233
|
}
|
|
159
234
|
}
|
|
160
235
|
|
|
236
|
+
/// ---
|
|
237
|
+
/// purpose: 整队重建的实体实现
|
|
238
|
+
/// params:
|
|
239
|
+
/// session_converge_deadline_ms: 会话收敛等待上限
|
|
240
|
+
/// readiness_deadline_ms: 就绪等待上限
|
|
241
|
+
/// returns: 重建报告,或计划分类给出的拒绝结论
|
|
242
|
+
/// errors: 归一路径失败返回 StatePersist;无本地 team 上下文或 spec 缺失返回 TeamSelect
|
|
243
|
+
/// ---
|
|
161
244
|
pub(crate) fn restart_with_transport_with_session_convergence_deadline(
|
|
162
245
|
workspace: &Path,
|
|
163
246
|
allow_fresh: bool,
|
|
@@ -2829,6 +2912,13 @@ fn apply_marked_respawn(
|
|
|
2829
2912
|
);
|
|
2830
2913
|
}
|
|
2831
2914
|
|
|
2915
|
+
/// ---
|
|
2916
|
+
/// purpose: 写一条 per-worker 的启动分段耗时事件
|
|
2917
|
+
/// params:
|
|
2918
|
+
/// source: 事件来源,launch 或 restart
|
|
2919
|
+
/// command_plan_ms: 命令拼装耗时,调用方给不出真实值时可能是占位 0
|
|
2920
|
+
/// returns: 写事件失败被吞掉
|
|
2921
|
+
/// ---
|
|
2832
2922
|
/// 0.5.38 Step 1 (`.team/artifacts/startup-latency-locate.md` §5): per-worker
|
|
2833
2923
|
/// timing tag so operators can pinpoint whether wall time is spent in
|
|
2834
2924
|
/// command plan compilation, transport spawn, pane verification, or the
|
|
@@ -2865,6 +2955,10 @@ pub(crate) fn write_worker_spawn_timing_event(
|
|
|
2865
2955
|
);
|
|
2866
2956
|
}
|
|
2867
2957
|
|
|
2958
|
+
/// ---
|
|
2959
|
+
/// purpose: 从 state 里取某席位的 provider wire 名
|
|
2960
|
+
/// returns: 取到的名字,缺失时为 fake
|
|
2961
|
+
/// ---
|
|
2868
2962
|
pub(crate) fn provider_wire_from_state<'a>(
|
|
2869
2963
|
state: &'a serde_json::Value,
|
|
2870
2964
|
agent_id: &str,
|
|
@@ -3112,6 +3206,11 @@ fn write_restart_resume_decision_event(
|
|
|
3112
3206
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))
|
|
3113
3207
|
}
|
|
3114
3208
|
|
|
3209
|
+
/// ---
|
|
3210
|
+
/// purpose: 列出本 workspace 里可重启的团队
|
|
3211
|
+
/// returns: 每个团队一条候选;没有活团队但顶层 state 有可重启形状时返回它自己
|
|
3212
|
+
/// errors: 读 runtime state 失败时返回 StatePersist
|
|
3213
|
+
/// ---
|
|
3115
3214
|
/// `restart_candidates(workspace)`(`restart/selection.py:12`)。从 snapshot + active
|
|
3116
3215
|
/// state 收集可重启 team。
|
|
3117
3216
|
pub(crate) fn restart_candidates(workspace: &Path) -> Result<Vec<RestartCandidate>, LifecycleError> {
|
|
@@ -3134,6 +3233,13 @@ pub(crate) fn restart_candidates(workspace: &Path) -> Result<Vec<RestartCandidat
|
|
|
3134
3233
|
.collect())
|
|
3135
3234
|
}
|
|
3136
3235
|
|
|
3236
|
+
/// ---
|
|
3237
|
+
/// purpose: 选出本次重启的目标团队
|
|
3238
|
+
/// params:
|
|
3239
|
+
/// team: 指定团队名,None 时按唯一性选
|
|
3240
|
+
/// returns: 选中的候选
|
|
3241
|
+
/// errors: 选不出或选中的 state 不具备可重启形状时返回 TeamSelect
|
|
3242
|
+
/// ---
|
|
3137
3243
|
/// `select_restart_state(workspace, team)`(`restart/selection.py:49`)。按 `--team` 或
|
|
3138
3244
|
/// 唯一性选一个;歧义/未找到 → `TeamSelect`。
|
|
3139
3245
|
pub(crate) fn select_restart_state(
|
|
@@ -1,11 +1,49 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 摘掉一席,含双确认门、六态一致性判定、原子摘除与字节级回滚快照
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: remove_agent
|
|
6
|
+
//! what: 从 spec、state、team_state 与 role 文件原子摘除一席
|
|
7
|
+
//! - name: remove_agent_flag_requirements
|
|
8
|
+
//! what: 只做前置判定,告诉调用方这次需要哪些确认标志
|
|
9
|
+
//! - name: ForceRecreateSnapshot
|
|
10
|
+
//! what: 强制重建用的快照,可在失败后恢复席位并清掉本事务新起的 pane
|
|
11
|
+
//! - name: resolve_seat
|
|
12
|
+
//! what: 由期望、持久与物理三源定出席位的唯一身份与一致性态
|
|
13
|
+
//! - name: spec_without_agent
|
|
14
|
+
//! what: 生成摘掉该席位后的 spec,并清掉指向它的路由与启动项
|
|
15
|
+
//! depends:
|
|
16
|
+
//! - crate::lifecycle::lock
|
|
17
|
+
//! - crate::lifecycle::restart::agent
|
|
18
|
+
//! - crate::lifecycle::restart::team_state
|
|
19
|
+
//! - crate::state::projection
|
|
20
|
+
//! - crate::transport::Transport
|
|
21
|
+
//! boundary:
|
|
22
|
+
//! - 未给 from_spec 确认或运行中未给 force 时拒绝,不擅自摘除
|
|
23
|
+
//! - 物理身份必须收敛到唯一的 session 加 window 加 pane,全局同名窗口不算身份
|
|
24
|
+
//! - 只删托管目录下的角色副本,用户自带的 role 文件不删
|
|
25
|
+
//! maturity: wired
|
|
26
|
+
//! ---
|
|
1
27
|
use super::agent::{resolve_team_scoped_state_or_refuse, start_agent_at_paths};
|
|
2
28
|
use super::common::*;
|
|
3
29
|
use super::team_state::write_team_state;
|
|
4
30
|
use super::*;
|
|
5
31
|
use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
|
|
6
32
|
|
|
33
|
+
/// ---
|
|
34
|
+
/// purpose: 摘掉一席的对外入口,取生命周期锁后走实体实现
|
|
35
|
+
/// params:
|
|
36
|
+
/// from_spec: 确认同时从 spec 里摘掉
|
|
37
|
+
/// force: 席位仍在运行时必须给出
|
|
38
|
+
/// returns: 摘除结果
|
|
39
|
+
/// errors: 选不到 team 返回 TeamSelect;确认标志不足或一致性态不允许时返回 RequirementUnmet;写盘失败返回 StatePersist
|
|
40
|
+
/// contract_id: lifecycle.remove_agent.entry
|
|
41
|
+
/// ---
|
|
7
42
|
/// `remove_agent(workspace, agent_id, from_spec, force, team)`(`lifecycle/agents.py:22`)。
|
|
8
|
-
/// 从 spec/state/team_state/agent_health
|
|
43
|
+
/// 从 spec/state/team_state/agent_health 原子摘除。
|
|
44
|
+
/// 托管目录 `.team/dynamic-role-files/` 下的物化副本随席位清掉;托管目录之外的
|
|
45
|
+
/// `--role-file` 仍是用户资产,不删。A-28 对托管文件「默认保留」的承诺已由
|
|
46
|
+
/// `ledger.seat-supply-prereq` 推翻。
|
|
9
47
|
/// `_RemoveRollback` 字节级快照回滚全部运行时变更。未传 from_spec 确认 / 运行中未传 force → 拒绝。
|
|
10
48
|
pub fn remove_agent(
|
|
11
49
|
workspace: &Path,
|
|
@@ -35,6 +73,12 @@ pub fn remove_agent(
|
|
|
35
73
|
)
|
|
36
74
|
}
|
|
37
75
|
|
|
76
|
+
/// ---
|
|
77
|
+
/// purpose: 带注入 transport 的摘席入口,自行取锁
|
|
78
|
+
/// returns: 摘除结果
|
|
79
|
+
/// errors: 同 remove_agent
|
|
80
|
+
/// contract_id: lifecycle.remove_agent.entry
|
|
81
|
+
/// ---
|
|
38
82
|
pub(crate) fn remove_agent_with_transport(
|
|
39
83
|
workspace: &Path,
|
|
40
84
|
agent_id: &AgentId,
|
|
@@ -63,6 +107,12 @@ pub(crate) fn remove_agent_with_transport(
|
|
|
63
107
|
)
|
|
64
108
|
}
|
|
65
109
|
|
|
110
|
+
/// ---
|
|
111
|
+
/// purpose: 调用方已持有生命周期锁时的摘席入口,本函数不再取锁
|
|
112
|
+
/// returns: 摘除结果
|
|
113
|
+
/// errors: 同 remove_agent
|
|
114
|
+
/// contract_id: lifecycle.remove_agent.entry
|
|
115
|
+
/// ---
|
|
66
116
|
pub(crate) fn remove_agent_with_transport_locked(
|
|
67
117
|
workspace: &Path,
|
|
68
118
|
agent_id: &AgentId,
|
|
@@ -93,6 +143,11 @@ pub(crate) struct ForceRecreateSnapshot {
|
|
|
93
143
|
}
|
|
94
144
|
|
|
95
145
|
impl ForceRecreateSnapshot {
|
|
146
|
+
/// ---
|
|
147
|
+
/// purpose: 为强制重建拍一份可回滚的快照
|
|
148
|
+
/// returns: 含逻辑回滚数据与摘除前物理 pane 身份的快照
|
|
149
|
+
/// errors: 选不到 team 或席位解析失败时返回 LifecycleError
|
|
150
|
+
/// ---
|
|
96
151
|
pub(crate) fn capture(
|
|
97
152
|
workspace: &Path,
|
|
98
153
|
agent_id: &AgentId,
|
|
@@ -126,6 +181,10 @@ impl ForceRecreateSnapshot {
|
|
|
126
181
|
})
|
|
127
182
|
}
|
|
128
183
|
|
|
184
|
+
/// ---
|
|
185
|
+
/// purpose: 按快照恢复席位的逻辑状态
|
|
186
|
+
/// returns: 恢复过程中的错误描述列表,空表示恢复干净
|
|
187
|
+
/// ---
|
|
129
188
|
pub(crate) fn restore(
|
|
130
189
|
&self,
|
|
131
190
|
team: Option<&str>,
|
|
@@ -135,6 +194,10 @@ impl ForceRecreateSnapshot {
|
|
|
135
194
|
.restore(&self.run_workspace, &self.spec_workspace, team, transport)
|
|
136
195
|
}
|
|
137
196
|
|
|
197
|
+
/// ---
|
|
198
|
+
/// purpose: 旧 pane 已被消费后的恢复,先杀掉本次事务新起的 pane 再恢复逻辑快照
|
|
199
|
+
/// returns: 错误描述列表;恢复干净时还会校验物理身份是否回到摘除前的 session 与窗口
|
|
200
|
+
/// ---
|
|
138
201
|
/// The old pane has already been consumed. Any exact pane now resolved for
|
|
139
202
|
/// this seat belongs to this force-recreate transaction and must be removed
|
|
140
203
|
/// before the logical snapshot is restored, otherwise rollback can leave a
|
|
@@ -193,6 +256,11 @@ impl ForceRecreateSnapshot {
|
|
|
193
256
|
errors
|
|
194
257
|
}
|
|
195
258
|
|
|
259
|
+
/// ---
|
|
260
|
+
/// purpose: 强制重建之后要求席位处于一致态
|
|
261
|
+
/// returns: 一致时返回空值
|
|
262
|
+
/// errors: 解析失败透传;解析出的一致性态不是 Coherent 时返回 StatePersist
|
|
263
|
+
/// ---
|
|
196
264
|
pub(crate) fn require_coherent(
|
|
197
265
|
&self,
|
|
198
266
|
agent_id: &AgentId,
|
|
@@ -217,6 +285,11 @@ impl ForceRecreateSnapshot {
|
|
|
217
285
|
}
|
|
218
286
|
}
|
|
219
287
|
|
|
288
|
+
/// ---
|
|
289
|
+
/// purpose: 只做前置判定,给出这次摘席需要哪些确认标志
|
|
290
|
+
/// returns: 标志要求;不做任何摘除动作
|
|
291
|
+
/// errors: 选不到 team 或席位解析失败时返回 LifecycleError
|
|
292
|
+
/// ---
|
|
220
293
|
pub fn remove_agent_flag_requirements(
|
|
221
294
|
workspace: &Path,
|
|
222
295
|
agent_id: &AgentId,
|
|
@@ -263,6 +336,13 @@ pub(super) struct ResolvedSeat {
|
|
|
263
336
|
pub(super) consistency: SeatConsistency,
|
|
264
337
|
}
|
|
265
338
|
|
|
339
|
+
/// ---
|
|
340
|
+
/// purpose: 由 spec、runtime state 与物理 pane 三源定出席位身份与一致性态
|
|
341
|
+
/// params:
|
|
342
|
+
/// transport: 已绑定该 team endpoint 的 transport
|
|
343
|
+
/// returns: 席位解析结果,含所在 session、窗口、物理 pane 与六态之一的一致性判定
|
|
344
|
+
/// errors: 团队作用域 state 取不到或 owner 门不过时返回 LifecycleError
|
|
345
|
+
/// ---
|
|
266
346
|
/// Resolve one seat from the selected team's desired, persisted and physical
|
|
267
347
|
/// sources. The transport is already bound to the selected team's endpoint;
|
|
268
348
|
/// physical identity is then narrowed to exactly one `(session, window, pane)`
|
|
@@ -552,6 +632,7 @@ fn remove_agent_inner(
|
|
|
552
632
|
// after the stop (stop_agent persisted it); otherwise the originally-resolved projection drives the
|
|
553
633
|
// removal. Either way we operate on the PROJECTION, never a raw load_runtime_state.
|
|
554
634
|
let working_state = state;
|
|
635
|
+
let recorded_role_file = recorded_dynamic_role_file(&working_state, agent_id);
|
|
555
636
|
let mut stopped = false;
|
|
556
637
|
let mut cleared_locations = Vec::new();
|
|
557
638
|
if force {
|
|
@@ -620,10 +701,16 @@ fn remove_agent_inner(
|
|
|
620
701
|
"team.spec.yaml",
|
|
621
702
|
None,
|
|
622
703
|
)?;
|
|
623
|
-
//
|
|
624
|
-
//
|
|
625
|
-
//
|
|
626
|
-
|
|
704
|
+
// Managed copies under `.team/dynamic-role-files/` are framework residue
|
|
705
|
+
// and must not block the next same-id clone. External --role-file paths
|
|
706
|
+
// stay user-owned. Classify by the path's directory (do not follow a
|
|
707
|
+
// last-component symlink); unlink with remove_file so only the link dies.
|
|
708
|
+
let role_file_removed = clear_managed_role_residue(
|
|
709
|
+
paths.run_workspace,
|
|
710
|
+
agent_id,
|
|
711
|
+
recorded_role_file.as_deref(),
|
|
712
|
+
&mut cleared_locations,
|
|
713
|
+
)?;
|
|
627
714
|
let agent_health_deleted = delete_agent_health(paths.run_workspace, team_key, agent_id)?;
|
|
628
715
|
cleared_locations.push(serde_json::json!("agent_health"));
|
|
629
716
|
write_remove_step_event(
|
|
@@ -663,6 +750,94 @@ struct RemoveSuccess {
|
|
|
663
750
|
cleared_locations: Vec<serde_json::Value>,
|
|
664
751
|
}
|
|
665
752
|
|
|
753
|
+
fn recorded_dynamic_role_file(
|
|
754
|
+
state: &serde_json::Value,
|
|
755
|
+
agent_id: &AgentId,
|
|
756
|
+
) -> Option<std::path::PathBuf> {
|
|
757
|
+
state
|
|
758
|
+
.get("agents")
|
|
759
|
+
.and_then(|v| v.get(agent_id.as_str()))
|
|
760
|
+
.and_then(|v| v.get("dynamic_role_file"))
|
|
761
|
+
.and_then(|v| v.as_str())
|
|
762
|
+
.map(str::trim)
|
|
763
|
+
.filter(|s| !s.is_empty())
|
|
764
|
+
.map(std::path::PathBuf::from)
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
fn resolve_role_path(workspace: &Path, role_file: &Path) -> std::path::PathBuf {
|
|
768
|
+
if role_file.is_absolute() {
|
|
769
|
+
role_file.to_path_buf()
|
|
770
|
+
} else {
|
|
771
|
+
workspace.join(role_file)
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
fn default_managed_role_file(workspace: &Path, agent_id: &AgentId) -> std::path::PathBuf {
|
|
776
|
+
workspace
|
|
777
|
+
.join(".team")
|
|
778
|
+
.join("dynamic-role-files")
|
|
779
|
+
.join(format!("{}.md", agent_id.as_str()))
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/// Same prefix rule as `role_source_ownership`, but canonicalize the parent
|
|
783
|
+
/// only. Following the last component would classify a managed symlink whose
|
|
784
|
+
/// target lives outside the managed dir as external, and leave residue.
|
|
785
|
+
fn role_path_is_managed(workspace: &Path, role_file: &Path) -> bool {
|
|
786
|
+
let managed_root = workspace.join(".team").join("dynamic-role-files");
|
|
787
|
+
let Ok(root) = std::fs::canonicalize(&managed_root) else {
|
|
788
|
+
return false;
|
|
789
|
+
};
|
|
790
|
+
let abs = resolve_role_path(workspace, role_file);
|
|
791
|
+
let Some(parent) = abs.parent() else {
|
|
792
|
+
return false;
|
|
793
|
+
};
|
|
794
|
+
match std::fs::canonicalize(parent) {
|
|
795
|
+
Ok(parent_canon) => parent_canon.starts_with(&root),
|
|
796
|
+
Err(_) => false,
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
fn unlink_role_path(path: &Path) -> Result<bool, LifecycleError> {
|
|
801
|
+
match std::fs::symlink_metadata(path) {
|
|
802
|
+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
|
|
803
|
+
Err(err) => Err(LifecycleError::StatePersist(format!(
|
|
804
|
+
"inspect managed role file {}: {err}",
|
|
805
|
+
path.display()
|
|
806
|
+
))),
|
|
807
|
+
Ok(_) => std::fs::remove_file(path).map(|_| true).map_err(|err| {
|
|
808
|
+
LifecycleError::StatePersist(format!(
|
|
809
|
+
"remove managed role file {}: {err}",
|
|
810
|
+
path.display()
|
|
811
|
+
))
|
|
812
|
+
}),
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
fn clear_managed_role_residue(
|
|
817
|
+
workspace: &Path,
|
|
818
|
+
agent_id: &AgentId,
|
|
819
|
+
recorded: Option<&Path>,
|
|
820
|
+
cleared_locations: &mut Vec<serde_json::Value>,
|
|
821
|
+
) -> Result<bool, LifecycleError> {
|
|
822
|
+
let target = match recorded {
|
|
823
|
+
Some(recorded) => {
|
|
824
|
+
let abs = resolve_role_path(workspace, recorded);
|
|
825
|
+
if !role_path_is_managed(workspace, &abs) {
|
|
826
|
+
return Ok(false);
|
|
827
|
+
}
|
|
828
|
+
abs
|
|
829
|
+
}
|
|
830
|
+
None => default_managed_role_file(workspace, agent_id),
|
|
831
|
+
};
|
|
832
|
+
if !unlink_role_path(&target)? {
|
|
833
|
+
return Ok(false);
|
|
834
|
+
}
|
|
835
|
+
let resource = target.to_string_lossy().into_owned();
|
|
836
|
+
write_remove_step_event(workspace, agent_id, "role_file", &resource, None)?;
|
|
837
|
+
cleared_locations.push(serde_json::json!(resource));
|
|
838
|
+
Ok(true)
|
|
839
|
+
}
|
|
840
|
+
|
|
666
841
|
fn write_remove_step_event(
|
|
667
842
|
workspace: &Path,
|
|
668
843
|
agent_id: &AgentId,
|
|
@@ -780,6 +955,13 @@ fn remove_agent_from_state(
|
|
|
780
955
|
}
|
|
781
956
|
}
|
|
782
957
|
|
|
958
|
+
/// ---
|
|
959
|
+
/// purpose: 在 state 里给该席位打上退役标记
|
|
960
|
+
/// params:
|
|
961
|
+
/// state: 就地写 agent_lifecycle 下该席位的状态、时间与原因
|
|
962
|
+
/// returns: 已是退役态时幂等返回
|
|
963
|
+
/// errors: state 根、agent_lifecycle 或该条目不是对象时返回 StatePersist
|
|
964
|
+
/// ---
|
|
783
965
|
pub(crate) fn mark_agent_retired_in_state(
|
|
784
966
|
state: &mut serde_json::Value,
|
|
785
967
|
agent_id: &AgentId,
|
|
@@ -814,6 +996,11 @@ pub(crate) fn mark_agent_retired_in_state(
|
|
|
814
996
|
Ok(())
|
|
815
997
|
}
|
|
816
998
|
|
|
999
|
+
/// ---
|
|
1000
|
+
/// purpose: 清掉该席位的退役标记
|
|
1001
|
+
/// params:
|
|
1002
|
+
/// state: 就地删除;只有当前确为退役态才删
|
|
1003
|
+
/// ---
|
|
817
1004
|
pub(crate) fn clear_agent_retirement_in_state(state: &mut serde_json::Value, agent_id: &AgentId) {
|
|
818
1005
|
let Some(lifecycle) = state
|
|
819
1006
|
.get_mut("agent_lifecycle")
|
|
@@ -831,6 +1018,10 @@ pub(crate) fn clear_agent_retirement_in_state(state: &mut serde_json::Value, age
|
|
|
831
1018
|
}
|
|
832
1019
|
}
|
|
833
1020
|
|
|
1021
|
+
/// ---
|
|
1022
|
+
/// purpose: 生成摘掉该席位后的 spec
|
|
1023
|
+
/// returns: 去掉该 agent、去掉它的启动项、并清掉指向它的路由引用后的 spec;spec 不是 map 时原样返回
|
|
1024
|
+
/// ---
|
|
834
1025
|
/// Build the persisted spec after removing one worker. Besides deleting the worker and startup entry,
|
|
835
1026
|
/// prune routing references that would otherwise point at the removed worker.
|
|
836
1027
|
pub(crate) fn spec_without_agent(spec: &YamlValue, agent_id: &AgentId) -> YamlValue {
|