@team-agent/installer 0.5.66 → 0.5.69
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 +156 -0
- package/crates/team-agent/src/cli/emit.rs +6 -5
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +22 -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 +3 -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 +121 -5
- 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 +31 -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 +3 -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 +973 -75
- 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 +398 -26
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +80 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +60 -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 +548 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +120 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +208 -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 +482 -1
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +830 -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 +87 -3
- 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 +127 -25
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +140 -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 +107 -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 +99 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +52 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +214 -160
- package/crates/team-agent/src/lifecycle/restart/common.rs +513 -37
- 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 +233 -6
- 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/claude_profile_launch_red.rs +24 -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 +464 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +247 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_g0.stdout.txt +222 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +787 -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 +485 -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/harvest2_a_batch_red.rs +1 -1
- 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/pi_catalog_red.rs +97 -0
- package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +128 -0
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +207 -0
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +838 -0
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +320 -0
- package/crates/team-agent/src/lifecycle/tests/pi_lifecycle_doctor_red.rs +98 -0
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +216 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -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 +30 -13
- package/crates/team-agent/src/lifecycle/types.rs +61 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +143 -12
- 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 +430 -41
- 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 +17 -6
- 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 +259 -6
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +89 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +3 -0
- package/crates/team-agent/src/provider/adapters/pi.rs +152 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +47 -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 +78 -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 +205 -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/pi.rs +172 -0
- package/crates/team-agent/src/provider/session_scan.rs +12 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +384 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +29 -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 +1116 -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 +32 -239
- 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,4 +1,43 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: restart 与 start 共用的底座,含 spawn 执行、transport 解析、state 标记与各类读取判定
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: spawn_agent_window
|
|
6
|
+
//! what: 为一个席位拼命令并在目标 session 里开窗或分格起进程
|
|
7
|
+
//! - name: lifecycle_worker_tmux_backend_for_selected_state
|
|
8
|
+
//! what: 按团队已持久化的 endpoint 解析 tmux 后端,非 tmux 状态直接拒绝
|
|
9
|
+
//! - name: save_restart_projected_state
|
|
10
|
+
//! what: 同步团队投影后经 repository 带写意图落盘
|
|
11
|
+
//! - name: resume_backing_probe_for_agent
|
|
12
|
+
//! what: 探测该席位的 resume 依据是否在盘上,并记下所有查过的路径
|
|
13
|
+
//! - name: converge_missing_provider_sessions
|
|
14
|
+
//! what: 有界等待会话捕获收敛,并逐轮写进度事件
|
|
15
|
+
//! - name: mark_agent_stopped
|
|
16
|
+
//! what: 把席位标记为已停并清掉 window 与 pane_id 字段
|
|
17
|
+
//! depends:
|
|
18
|
+
//! - crate::transport::Transport
|
|
19
|
+
//! - crate::tmux_backend
|
|
20
|
+
//! - crate::transport_factory
|
|
21
|
+
//! - crate::provider
|
|
22
|
+
//! - crate::session_capture
|
|
23
|
+
//! - crate::state::projection
|
|
24
|
+
//! - crate::state::repository
|
|
25
|
+
//! - crate::state::persist
|
|
26
|
+
//! - crate::coordinator::health
|
|
27
|
+
//! - crate::event_log::EventLog
|
|
28
|
+
//! - crate::lifecycle::launch
|
|
29
|
+
//! - crate::lifecycle::profile_launch
|
|
30
|
+
//! - crate::lifecycle::worker_command_context
|
|
31
|
+
//! - crate::lifecycle::restart::selection
|
|
32
|
+
//! boundary:
|
|
33
|
+
//! - 状态是 conpty 时 tmux 专用解析器直接报错,不静默降级到 tmux
|
|
34
|
+
//! - 探测类判定失败一律倒向保守值,不把不确定当成存活
|
|
35
|
+
//! - 清活动观测只删观测字段,不动生命周期与拓扑字段
|
|
36
|
+
//! - 收敛与排空都是有界等待,不无限阻塞
|
|
37
|
+
//! maturity: wired
|
|
38
|
+
//! ---
|
|
1
39
|
use super::*;
|
|
40
|
+
use crate::transport::Transport;
|
|
2
41
|
|
|
3
42
|
pub(super) struct SpawnedAgentWindow {
|
|
4
43
|
pub spawn: crate::transport::SpawnResult,
|
|
@@ -23,6 +62,12 @@ pub(super) struct SameRoleCohortTarget {
|
|
|
23
62
|
}
|
|
24
63
|
|
|
25
64
|
impl SameRoleCohortTarget {
|
|
65
|
+
/// ---
|
|
66
|
+
/// purpose: 构造一个同角色同批目标
|
|
67
|
+
/// params:
|
|
68
|
+
/// window: 该席位的窗口名
|
|
69
|
+
/// returns: 未带期望 pane 的目标
|
|
70
|
+
/// ---
|
|
26
71
|
pub(super) fn new(agent_id: &AgentId, window: &str) -> Self {
|
|
27
72
|
Self {
|
|
28
73
|
agent_id: agent_id.as_str().to_string(),
|
|
@@ -31,16 +76,28 @@ impl SameRoleCohortTarget {
|
|
|
31
76
|
}
|
|
32
77
|
}
|
|
33
78
|
|
|
79
|
+
/// ---
|
|
80
|
+
/// purpose: 给目标补上期望的旧 pane
|
|
81
|
+
/// returns: 带期望 pane 的目标
|
|
82
|
+
/// ---
|
|
34
83
|
pub(super) fn with_expected_pane_id(mut self, pane_id: Option<&str>) -> Self {
|
|
35
84
|
self.expected_pane_id = pane_id.map(ToString::to_string);
|
|
36
85
|
self
|
|
37
86
|
}
|
|
38
87
|
}
|
|
39
88
|
|
|
89
|
+
/// ---
|
|
90
|
+
/// purpose: 判断该窗口是不是按席位命名的独立窗口
|
|
91
|
+
/// returns: 窗口名等于席位名且不是规范布局窗口时为 true
|
|
92
|
+
/// ---
|
|
40
93
|
pub(super) fn is_per_agent_cohort_window(window: &str, agent_id: &AgentId) -> bool {
|
|
41
94
|
window == agent_id.as_str() && !crate::lifecycle::launch::is_adaptive_layout_window_pub(window)
|
|
42
95
|
}
|
|
43
96
|
|
|
97
|
+
/// ---
|
|
98
|
+
/// purpose: spawn 之前检查同角色同批是否已有残留
|
|
99
|
+
/// returns: 期望基数为 0;不满足时给出可读的拒绝说明,满足则 None
|
|
100
|
+
/// ---
|
|
44
101
|
pub(super) fn same_role_cohort_pre_spawn_error(
|
|
45
102
|
transport: &dyn crate::transport::Transport,
|
|
46
103
|
session_name: &SessionName,
|
|
@@ -50,6 +107,10 @@ pub(super) fn same_role_cohort_pre_spawn_error(
|
|
|
50
107
|
same_role_cohort_error(transport, session_name, operation, targets, 0, true)
|
|
51
108
|
}
|
|
52
109
|
|
|
110
|
+
/// ---
|
|
111
|
+
/// purpose: spawn 之后检查同角色同批是否恰好只剩一个
|
|
112
|
+
/// returns: 期望基数为 1;不满足时给出可读的拒绝说明,满足则 None
|
|
113
|
+
/// ---
|
|
53
114
|
pub(super) fn same_role_cohort_exactly_one_error(
|
|
54
115
|
transport: &dyn crate::transport::Transport,
|
|
55
116
|
session_name: &SessionName,
|
|
@@ -59,6 +120,11 @@ pub(super) fn same_role_cohort_exactly_one_error(
|
|
|
59
120
|
same_role_cohort_error(transport, session_name, operation, targets, 1, false)
|
|
60
121
|
}
|
|
61
122
|
|
|
123
|
+
/// ---
|
|
124
|
+
/// purpose: 杀掉这些目标记录的旧 pane
|
|
125
|
+
/// returns: 全部处理完返回空值;目标没有期望 pane 或该 pane 明确不存在时跳过
|
|
126
|
+
/// errors: 杀 pane 失败时返回带席位、窗口与 pane 的错误串
|
|
127
|
+
/// ---
|
|
62
128
|
pub(super) fn retire_expected_same_role_cohorts(
|
|
63
129
|
transport: &dyn crate::transport::Transport,
|
|
64
130
|
operation: &str,
|
|
@@ -175,6 +241,17 @@ fn same_role_cohort_error(
|
|
|
175
241
|
None
|
|
176
242
|
}
|
|
177
243
|
|
|
244
|
+
/// ---
|
|
245
|
+
/// purpose: 为一个席位拼出命令并在目标 session 里起进程
|
|
246
|
+
/// params:
|
|
247
|
+
/// resume_session_id: 给出且该 provider 支持 resume 时按 resume 起,否则丢弃它全新起
|
|
248
|
+
/// into_existing_session: 目标 session 已存在时开新窗口,否则新建 session
|
|
249
|
+
/// layout_placement: 有布局位置时按布局开窗或分格
|
|
250
|
+
/// spawn_cwd_override: 覆盖工作目录
|
|
251
|
+
/// owner_team_id_override: 显式指定写进 worker 环境的 owner team,缺省时退回席位行与顶层活跃键
|
|
252
|
+
/// returns: spawn 结果、时间戳、命令计划、profile 启动参数、布局位置与实际使用的 owner team
|
|
253
|
+
/// errors: 命令拼装、profile 准备或 transport spawn 失败时返回 LifecycleError
|
|
254
|
+
/// ---
|
|
178
255
|
#[allow(clippy::too_many_arguments)]
|
|
179
256
|
pub(super) fn spawn_agent_window(
|
|
180
257
|
workspace: &Path,
|
|
@@ -214,8 +291,7 @@ pub(super) fn spawn_agent_window(
|
|
|
214
291
|
safety
|
|
215
292
|
} else {
|
|
216
293
|
detected_safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn_json(
|
|
217
|
-
agent,
|
|
218
|
-
provider,
|
|
294
|
+
agent, provider,
|
|
219
295
|
)?;
|
|
220
296
|
&detected_safety
|
|
221
297
|
};
|
|
@@ -265,11 +341,15 @@ pub(super) fn spawn_agent_window(
|
|
|
265
341
|
agent_id.as_str(),
|
|
266
342
|
team_id.as_deref().unwrap_or(""),
|
|
267
343
|
);
|
|
268
|
-
let mcp_config_path =
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
344
|
+
let mcp_config_path = if provider == Provider::Pi {
|
|
345
|
+
None
|
|
346
|
+
} else {
|
|
347
|
+
Some(crate::lifecycle::launch::write_worker_mcp_config(
|
|
348
|
+
workspace,
|
|
349
|
+
agent_id.as_str(),
|
|
350
|
+
&mcp_config,
|
|
351
|
+
)?)
|
|
352
|
+
};
|
|
273
353
|
let profile_launch =
|
|
274
354
|
crate::lifecycle::profile_launch::prepare_provider_profile_launch_from_json(
|
|
275
355
|
workspace,
|
|
@@ -299,20 +379,75 @@ pub(super) fn spawn_agent_window(
|
|
|
299
379
|
agent_id_hint: Some(agent_id.as_str()),
|
|
300
380
|
effort: restart_effort,
|
|
301
381
|
};
|
|
302
|
-
let
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
382
|
+
let pi_spawn_cwd = (provider == Provider::Pi)
|
|
383
|
+
.then(|| {
|
|
384
|
+
agent
|
|
385
|
+
.get("spawn_cwd")
|
|
386
|
+
.and_then(serde_json::Value::as_str)
|
|
387
|
+
.filter(|value| !value.is_empty())
|
|
388
|
+
.map(PathBuf::from)
|
|
389
|
+
})
|
|
390
|
+
.flatten();
|
|
391
|
+
let spawn_cwd = spawn_cwd_override
|
|
392
|
+
.or(pi_spawn_cwd.as_deref())
|
|
393
|
+
.unwrap_or(workspace);
|
|
394
|
+
let mut plan = if provider == Provider::Pi {
|
|
395
|
+
let model = command_model.ok_or_else(|| {
|
|
396
|
+
LifecycleError::RequirementUnmet(
|
|
397
|
+
"Pi restart requires an explicit qualified model".to_string(),
|
|
398
|
+
)
|
|
399
|
+
})?;
|
|
400
|
+
let effort = restart_effort.ok_or_else(|| {
|
|
401
|
+
LifecycleError::RequirementUnmet(
|
|
402
|
+
"Pi restart requires an explicit thinking effort".to_string(),
|
|
403
|
+
)
|
|
404
|
+
})?;
|
|
405
|
+
let request = crate::lifecycle::launch::pi_mcp::PiMaterializeRequest {
|
|
406
|
+
workspace,
|
|
407
|
+
team_id: team_id.as_deref().unwrap_or(""),
|
|
408
|
+
agent_id: agent_id.as_str(),
|
|
409
|
+
model,
|
|
410
|
+
effort,
|
|
411
|
+
system_prompt: &system_prompt,
|
|
412
|
+
tool_categories: &resolved_tool_refs,
|
|
413
|
+
team_mcp_tools: &["send_message", "report_result"],
|
|
414
|
+
mcp_config: &mcp_config,
|
|
415
|
+
};
|
|
416
|
+
match resume_session_id {
|
|
417
|
+
Some(session_id) => {
|
|
418
|
+
let rollout_path = agent_rollout_path(agent).ok_or_else(|| {
|
|
419
|
+
LifecycleError::RequirementUnmet(
|
|
420
|
+
"Pi resume requires the persisted exact session path".to_string(),
|
|
421
|
+
)
|
|
422
|
+
})?;
|
|
423
|
+
crate::lifecycle::launch::pi_mcp::materialize_pi_resume_plan(
|
|
424
|
+
request,
|
|
425
|
+
session_id,
|
|
426
|
+
rollout_path.as_path(),
|
|
427
|
+
spawn_cwd,
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
None => crate::lifecycle::launch::pi_mcp::materialize_pi_plan(request),
|
|
431
|
+
}
|
|
432
|
+
.map_err(|e| LifecycleError::Provider(e.to_string()))?
|
|
433
|
+
} else {
|
|
434
|
+
match resume_session_id {
|
|
435
|
+
Some(session_id) => adapter
|
|
436
|
+
.build_resume_command_plan(Some(session_id), context)
|
|
437
|
+
.map_err(|e| LifecycleError::Provider(e.to_string()))?,
|
|
438
|
+
None => adapter
|
|
439
|
+
.build_command_plan(context)
|
|
440
|
+
.map_err(|e| LifecycleError::Provider(e.to_string()))?,
|
|
441
|
+
}
|
|
309
442
|
};
|
|
310
443
|
if !plan.managed_mcp_config && !profile_launch.managed_mcp_config {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
444
|
+
if let Some(mcp_config_path) = mcp_config_path.as_ref() {
|
|
445
|
+
crate::lifecycle::launch::point_native_mcp_config_at_file(
|
|
446
|
+
&mut plan.argv,
|
|
447
|
+
provider,
|
|
448
|
+
mcp_config_path,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
316
451
|
}
|
|
317
452
|
crate::lifecycle::launch::fill_spawn_placeholders_full(
|
|
318
453
|
&mut plan.argv,
|
|
@@ -327,6 +462,7 @@ pub(super) fn spawn_agent_window(
|
|
|
327
462
|
workspace,
|
|
328
463
|
agent_id.as_str(),
|
|
329
464
|
team_id.as_deref(),
|
|
465
|
+
Some(crate::lifecycle::launch::auth_mode_env_value(auth_mode)),
|
|
330
466
|
);
|
|
331
467
|
crate::lifecycle::launch::apply_profile_launch_env(&mut env, &profile_launch);
|
|
332
468
|
crate::lifecycle::launch::apply_mcp_auto_approval_env(&mut env, safety);
|
|
@@ -338,6 +474,39 @@ pub(super) fn spawn_agent_window(
|
|
|
338
474
|
&mut env,
|
|
339
475
|
)?;
|
|
340
476
|
}
|
|
477
|
+
// 0.5.67 Cursor 方案 1 变体: role 经 workspace rules 文件注入 (不 argv)。
|
|
478
|
+
if provider == crate::provider::Provider::CursorAgent {
|
|
479
|
+
crate::lifecycle::launch::refuse_second_cursor_occupant(
|
|
480
|
+
workspace,
|
|
481
|
+
agent_id.as_str(),
|
|
482
|
+
None,
|
|
483
|
+
)?;
|
|
484
|
+
crate::lifecycle::launch::apply_cursor_agent_rules_overlay(
|
|
485
|
+
workspace,
|
|
486
|
+
agent_id.as_str(),
|
|
487
|
+
&system_prompt,
|
|
488
|
+
)?;
|
|
489
|
+
crate::lifecycle::launch::apply_cursor_mcp_overlay(workspace, &mcp_config)?;
|
|
490
|
+
let project = if crate::lifecycle::launch::cursor_mcp_isolation_enabled() {
|
|
491
|
+
Some(crate::lifecycle::launch::cursor_mcp_project_dir(
|
|
492
|
+
workspace,
|
|
493
|
+
agent_id.as_str(),
|
|
494
|
+
)?)
|
|
495
|
+
} else {
|
|
496
|
+
None
|
|
497
|
+
};
|
|
498
|
+
crate::lifecycle::launch::enable_cursor_workspace_mcp(workspace, project.as_deref())?;
|
|
499
|
+
crate::lifecycle::launch::apply_cursor_spawn_workspace_pointers(
|
|
500
|
+
&mut plan.argv,
|
|
501
|
+
workspace,
|
|
502
|
+
agent_id.as_str(),
|
|
503
|
+
)?;
|
|
504
|
+
crate::lifecycle::launch::apply_cursor_subscription_proxy_env(&mut env);
|
|
505
|
+
}
|
|
506
|
+
if provider == crate::provider::Provider::Grok {
|
|
507
|
+
crate::lifecycle::launch::ensure_grok_login_and_folder_trust(workspace)?;
|
|
508
|
+
crate::lifecycle::launch::apply_grok_mcp_overlay(workspace, &mcp_config)?;
|
|
509
|
+
}
|
|
341
510
|
// 0.3.28 Step 3: per Python parity, worker spawn cwd is ALWAYS `workspace`.
|
|
342
511
|
// The persisted-state `agent.spawn_cwd` override is ignored (it was a
|
|
343
512
|
// Rust-only extension that drifted to `.team/runtime/<team_key>/` after
|
|
@@ -349,8 +518,9 @@ pub(super) fn spawn_agent_window(
|
|
|
349
518
|
//
|
|
350
519
|
// NOTE: Step 4 will thread the YAML spec down to here so we can honour
|
|
351
520
|
// a per-agent YAML `spawn_cwd` field if one is set. Until then, override
|
|
352
|
-
// > workspace; state-based override is silently dropped
|
|
353
|
-
|
|
521
|
+
// > workspace; state-based override is silently dropped for existing
|
|
522
|
+
// providers. Pi is the narrow exception because exact header/path resume
|
|
523
|
+
// must preserve the captured cwd or refuse.
|
|
354
524
|
// 0.4.x provider effort MVP step 9: scrub CLAUDE_EFFORT for Claude
|
|
355
525
|
// worker spawn so a parent shell env cannot silently override the
|
|
356
526
|
// framework's effort decision.
|
|
@@ -560,6 +730,10 @@ fn is_structural_startup_prompt_error(error: &str) -> bool {
|
|
|
560
730
|
.any(|needle| lower.contains(needle))
|
|
561
731
|
}
|
|
562
732
|
|
|
733
|
+
/// ---
|
|
734
|
+
/// purpose: 用 spec 里的同名 agent 补回 state 席位行缺的命令上下文字段
|
|
735
|
+
/// returns: 合并后的席位行;spec 读不到或找不到该 agent 时原样返回
|
|
736
|
+
/// ---
|
|
563
737
|
pub(super) fn rehydrate_agent_command_context_from_spec(
|
|
564
738
|
spec_workspace: &Path,
|
|
565
739
|
agent_id: &AgentId,
|
|
@@ -652,6 +826,13 @@ fn window_present_in_live(
|
|
|
652
826
|
false
|
|
653
827
|
}
|
|
654
828
|
|
|
829
|
+
/// ---
|
|
830
|
+
/// purpose: 起该 workspace 的 coordinator
|
|
831
|
+
/// params:
|
|
832
|
+
/// team_key: 传给 coordinator 的团队键
|
|
833
|
+
/// returns: 启动摘要
|
|
834
|
+
/// errors: 启动失败时返回 StatePersist
|
|
835
|
+
/// ---
|
|
655
836
|
pub(super) fn start_coordinator_for_workspace(
|
|
656
837
|
workspace: &Path,
|
|
657
838
|
team_key: Option<&str>,
|
|
@@ -662,6 +843,13 @@ pub(super) fn start_coordinator_for_workspace(
|
|
|
662
843
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))
|
|
663
844
|
}
|
|
664
845
|
|
|
846
|
+
/// ---
|
|
847
|
+
/// purpose: 按团队持久化的 tmux endpoint 解析出后端,让 restart、add 与 fork 落在同一 socket
|
|
848
|
+
/// params:
|
|
849
|
+
/// team: 目标团队,None 时按唯一性选
|
|
850
|
+
/// returns: 绑定到该 endpoint 的 tmux 后端;冷 workspace 无持久 endpoint 时退到按 workspace 派生
|
|
851
|
+
/// errors: 团队目标歧义或未解析返回 TeamSelect;state 声明后端是 conpty 时也返回 TeamSelect 拒绝,不降级
|
|
852
|
+
/// ---
|
|
665
853
|
/// State-aware tmux backend resolver. Reads the team's persisted
|
|
666
854
|
/// `tmux_endpoint` (set at `team-agent launch` time and shared across
|
|
667
855
|
/// restart/add-agent/fork-agent) and constructs a TmuxBackend on THAT socket,
|
|
@@ -736,6 +924,11 @@ pub(crate) fn lifecycle_worker_tmux_backend_for_selected_state(
|
|
|
736
924
|
.unwrap_or_else(|| crate::tmux_backend::TmuxBackend::for_workspace(run_workspace)))
|
|
737
925
|
}
|
|
738
926
|
|
|
927
|
+
/// ---
|
|
928
|
+
/// purpose: 与上面同样的团队选择语义,但返回工厂解析出的通用 transport
|
|
929
|
+
/// returns: 已解析的 transport,含后端种类、来源与提示
|
|
930
|
+
/// errors: 团队选择失败或工厂拒绝时返回 TeamSelect,读 state 失败返回 StatePersist
|
|
931
|
+
/// ---
|
|
739
932
|
/// 0.5.x Phase 1d Batch 1: new generic-typed lifecycle resolver.
|
|
740
933
|
///
|
|
741
934
|
/// Same team-selection semantics as the legacy tmux-typed variant, but
|
|
@@ -774,6 +967,11 @@ pub(crate) fn lifecycle_worker_transport_for_selected_state(
|
|
|
774
967
|
.map_err(|e| LifecycleError::TeamSelect(e.to_string()))
|
|
775
968
|
}
|
|
776
969
|
|
|
970
|
+
/// ---
|
|
971
|
+
/// purpose: 由已取到的 state 解析 tmux 后端与它的来源
|
|
972
|
+
/// returns: 后端与 endpoint 来源
|
|
973
|
+
/// errors: state 声明后端是 conpty 时返回 TeamSelect
|
|
974
|
+
/// ---
|
|
777
975
|
pub(super) fn lifecycle_worker_tmux_backend_selection_for_state(
|
|
778
976
|
run_workspace: &Path,
|
|
779
977
|
state: &serde_json::Value,
|
|
@@ -787,6 +985,14 @@ pub(super) fn lifecycle_worker_tmux_backend_selection_for_state(
|
|
|
787
985
|
)));
|
|
788
986
|
}
|
|
789
987
|
}
|
|
988
|
+
if let Some((endpoint, source)) = crate::tmux_backend::owning_tmux_endpoint_from_state(state) {
|
|
989
|
+
let backend = crate::tmux_backend::TmuxBackend::for_tmux_endpoint(endpoint);
|
|
990
|
+
return Ok(crate::tmux_backend::RuntimeTmuxBackendSelection {
|
|
991
|
+
tmux_endpoint_used: backend.tmux_endpoint(),
|
|
992
|
+
backend,
|
|
993
|
+
tmux_endpoint_source: source,
|
|
994
|
+
});
|
|
995
|
+
}
|
|
790
996
|
Ok(
|
|
791
997
|
crate::tmux_backend::tmux_backend_for_runtime_state_or_workspace(
|
|
792
998
|
run_workspace,
|
|
@@ -795,14 +1001,29 @@ pub(super) fn lifecycle_worker_tmux_backend_selection_for_state(
|
|
|
795
1001
|
)
|
|
796
1002
|
}
|
|
797
1003
|
|
|
1004
|
+
/// ---
|
|
1005
|
+
/// purpose: 由已取到的 state 直接给出 tmux 后端
|
|
1006
|
+
/// returns: 绑定到该 state endpoint 的后端,缺失时按 workspace 派生
|
|
1007
|
+
/// ---
|
|
798
1008
|
pub(super) fn lifecycle_worker_tmux_backend_for_state(
|
|
799
1009
|
run_workspace: &Path,
|
|
800
1010
|
state: &serde_json::Value,
|
|
801
1011
|
) -> crate::tmux_backend::TmuxBackend {
|
|
1012
|
+
if let Some((endpoint, _source)) = crate::tmux_backend::owning_tmux_endpoint_from_state(state) {
|
|
1013
|
+
return crate::tmux_backend::TmuxBackend::for_tmux_endpoint(endpoint);
|
|
1014
|
+
}
|
|
802
1015
|
crate::tmux_backend::tmux_backend_for_runtime_state_or_workspace(run_workspace, Some(state))
|
|
803
1016
|
.backend
|
|
804
1017
|
}
|
|
805
1018
|
|
|
1019
|
+
/// ---
|
|
1020
|
+
/// purpose: 同步团队投影后落盘 restart 结果
|
|
1021
|
+
/// params:
|
|
1022
|
+
/// topology_authority_agent_ids: 本次以内存值为拓扑权威的席位
|
|
1023
|
+
/// returns: 成功返回空值
|
|
1024
|
+
/// errors: 落盘失败时返回 StatePersist
|
|
1025
|
+
/// contract_id: lifecycle.common.save_restart_projected_state
|
|
1026
|
+
/// ---
|
|
806
1027
|
pub(super) fn save_restart_projected_state(
|
|
807
1028
|
workspace: &Path,
|
|
808
1029
|
state: &mut serde_json::Value,
|
|
@@ -818,6 +1039,14 @@ pub(super) fn save_restart_projected_state(
|
|
|
818
1039
|
)
|
|
819
1040
|
}
|
|
820
1041
|
|
|
1042
|
+
/// ---
|
|
1043
|
+
/// purpose: 同上,并可指定哪些席位跳过会话捕获回填
|
|
1044
|
+
/// params:
|
|
1045
|
+
/// skip_capture_backfill_agent_ids: 跳过回填的席位
|
|
1046
|
+
/// returns: 成功返回空值
|
|
1047
|
+
/// errors: 落盘失败时返回 StatePersist
|
|
1048
|
+
/// contract_id: lifecycle.common.save_restart_projected_state
|
|
1049
|
+
/// ---
|
|
821
1050
|
pub(super) fn save_restart_projected_state_with_capture_backfill_skip(
|
|
822
1051
|
workspace: &Path,
|
|
823
1052
|
state: &mut serde_json::Value,
|
|
@@ -826,17 +1055,22 @@ pub(super) fn save_restart_projected_state_with_capture_backfill_skip(
|
|
|
826
1055
|
topology_authority_agent_ids: &[&str],
|
|
827
1056
|
) -> Result<(), LifecycleError> {
|
|
828
1057
|
sync_restart_team_projections(state, team_key);
|
|
829
|
-
crate::state::repository::StateRepository::new(workspace)
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1058
|
+
crate::state::repository::StateRepository::new(workspace)
|
|
1059
|
+
.save(
|
|
1060
|
+
crate::state::repository::StateWriteIntent::RestartTeam {
|
|
1061
|
+
team_key,
|
|
1062
|
+
topology_authority_agent_ids,
|
|
1063
|
+
skip_capture_backfill_agent_ids,
|
|
1064
|
+
},
|
|
1065
|
+
state,
|
|
1066
|
+
)
|
|
1067
|
+
.map_err(|e| LifecycleError::StatePersist(e.to_string()))
|
|
838
1068
|
}
|
|
839
1069
|
|
|
1070
|
+
/// ---
|
|
1071
|
+
/// purpose: 定出本次投影使用的团队键
|
|
1072
|
+
/// returns: 显式 team 优先,其次 state 里的活跃键,最后由 state 推算
|
|
1073
|
+
/// ---
|
|
840
1074
|
pub(super) fn restart_projection_team_key(state: &serde_json::Value, team: Option<&str>) -> String {
|
|
841
1075
|
team.filter(|key| !key.is_empty())
|
|
842
1076
|
.map(str::to_string)
|
|
@@ -850,6 +1084,12 @@ pub(super) fn restart_projection_team_key(state: &serde_json::Value, team: Optio
|
|
|
850
1084
|
.unwrap_or_else(|| crate::state::projection::team_state_key(state))
|
|
851
1085
|
}
|
|
852
1086
|
|
|
1087
|
+
/// ---
|
|
1088
|
+
/// purpose: 把当前顶层状态压实后写回 teams 表
|
|
1089
|
+
/// params:
|
|
1090
|
+
/// state: 就地改写;显式团队键允许覆盖或新建,别名键只在盘上已有且身份不冲突时才写
|
|
1091
|
+
/// returns: teams 表缺失或为空时不动
|
|
1092
|
+
/// ---
|
|
853
1093
|
pub(super) fn sync_restart_team_projections(state: &mut serde_json::Value, team_key: &str) {
|
|
854
1094
|
let Some(teams) = state.get("teams").and_then(serde_json::Value::as_object) else {
|
|
855
1095
|
return;
|
|
@@ -930,23 +1170,55 @@ fn json_team_identity_matches(existing: &serde_json::Value, compact: &serde_json
|
|
|
930
1170
|
true
|
|
931
1171
|
}
|
|
932
1172
|
|
|
1173
|
+
/// ---
|
|
1174
|
+
/// purpose: 取 state 里的 session 名
|
|
1175
|
+
/// returns: 非空的 session_name,缺失时退到默认名
|
|
1176
|
+
/// ---
|
|
933
1177
|
pub(super) fn state_session_name(state: &serde_json::Value) -> SessionName {
|
|
934
|
-
state
|
|
935
|
-
.get("session_name")
|
|
936
|
-
.and_then(|v| v.as_str())
|
|
937
|
-
.filter(|s| !s.is_empty())
|
|
1178
|
+
worker_session_name_from_state(state)
|
|
938
1179
|
.map(SessionName::new)
|
|
939
1180
|
.unwrap_or_else(|| SessionName::new("team-agent"))
|
|
940
1181
|
}
|
|
941
1182
|
|
|
942
|
-
|
|
1183
|
+
fn worker_session_name_from_state(state: &serde_json::Value) -> Option<&str> {
|
|
943
1184
|
state
|
|
944
1185
|
.get("session_name")
|
|
945
1186
|
.and_then(|v| v.as_str())
|
|
946
|
-
.
|
|
947
|
-
.
|
|
1187
|
+
.filter(|s| !s.is_empty())
|
|
1188
|
+
.or_else(|| {
|
|
1189
|
+
let team_key = state
|
|
1190
|
+
.get("active_team_key")
|
|
1191
|
+
.and_then(serde_json::Value::as_str)
|
|
1192
|
+
.filter(|team_key| !team_key.is_empty())?;
|
|
1193
|
+
state
|
|
1194
|
+
.get("teams")?
|
|
1195
|
+
.get(team_key)?
|
|
1196
|
+
.pointer("/leader_receiver/session_name")?
|
|
1197
|
+
.as_str()
|
|
1198
|
+
.filter(|session| !session.is_empty())
|
|
1199
|
+
})
|
|
1200
|
+
.or_else(|| {
|
|
1201
|
+
state
|
|
1202
|
+
.pointer("/leader_receiver/session_name")
|
|
1203
|
+
.and_then(serde_json::Value::as_str)
|
|
1204
|
+
.filter(|session| !session.is_empty())
|
|
1205
|
+
})
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/// ---
|
|
1209
|
+
/// purpose: 判断 state 里是否记了非空 session 名
|
|
1210
|
+
/// returns: 记了则 true
|
|
1211
|
+
/// ---
|
|
1212
|
+
pub(super) fn session_name_present(state: &serde_json::Value) -> bool {
|
|
1213
|
+
worker_session_name_from_state(state).is_some()
|
|
948
1214
|
}
|
|
949
1215
|
|
|
1216
|
+
/// ---
|
|
1217
|
+
/// purpose: 探测 session 是否存活
|
|
1218
|
+
/// params:
|
|
1219
|
+
/// default: 探测本身 panic 时采用的兜底判定
|
|
1220
|
+
/// returns: transport 明确回答时用它;返回错误时判为不存活;探测 panic 时用兜底值
|
|
1221
|
+
/// ---
|
|
950
1222
|
pub(super) fn session_live_or_default(
|
|
951
1223
|
transport: &dyn crate::transport::Transport,
|
|
952
1224
|
session_name: &SessionName,
|
|
@@ -961,6 +1233,10 @@ pub(super) fn session_live_or_default(
|
|
|
961
1233
|
}
|
|
962
1234
|
}
|
|
963
1235
|
|
|
1236
|
+
/// ---
|
|
1237
|
+
/// purpose: 取席位行的 provider
|
|
1238
|
+
/// returns: 解析成功用它,缺失或不认识时退到 codex
|
|
1239
|
+
/// ---
|
|
964
1240
|
pub(super) fn agent_provider(agent: &serde_json::Value) -> Provider {
|
|
965
1241
|
agent
|
|
966
1242
|
.get("provider")
|
|
@@ -969,6 +1245,10 @@ pub(super) fn agent_provider(agent: &serde_json::Value) -> Provider {
|
|
|
969
1245
|
.unwrap_or(Provider::Codex)
|
|
970
1246
|
}
|
|
971
1247
|
|
|
1248
|
+
/// ---
|
|
1249
|
+
/// purpose: 取席位行的 auth_mode
|
|
1250
|
+
/// returns: 解析成功用它,缺失或不认识时退到 subscription
|
|
1251
|
+
/// ---
|
|
972
1252
|
pub(super) fn agent_auth_mode(agent: &serde_json::Value) -> AuthMode {
|
|
973
1253
|
agent
|
|
974
1254
|
.get("auth_mode")
|
|
@@ -977,6 +1257,10 @@ pub(super) fn agent_auth_mode(agent: &serde_json::Value) -> AuthMode {
|
|
|
977
1257
|
.unwrap_or(AuthMode::Subscription)
|
|
978
1258
|
}
|
|
979
1259
|
|
|
1260
|
+
/// ---
|
|
1261
|
+
/// purpose: 取席位行记录的会话 id
|
|
1262
|
+
/// returns: 非空时返回,否则 None
|
|
1263
|
+
/// ---
|
|
980
1264
|
pub(super) fn agent_session_id(agent: &serde_json::Value) -> Option<SessionId> {
|
|
981
1265
|
agent
|
|
982
1266
|
.get("session_id")
|
|
@@ -985,6 +1269,10 @@ pub(super) fn agent_session_id(agent: &serde_json::Value) -> Option<SessionId> {
|
|
|
985
1269
|
.map(SessionId::new)
|
|
986
1270
|
}
|
|
987
1271
|
|
|
1272
|
+
/// ---
|
|
1273
|
+
/// purpose: 取席位行记录的 rollout 路径
|
|
1274
|
+
/// returns: 非空时返回,否则 None
|
|
1275
|
+
/// ---
|
|
988
1276
|
pub(super) fn agent_rollout_path(agent: &serde_json::Value) -> Option<RolloutPath> {
|
|
989
1277
|
agent
|
|
990
1278
|
.get("rollout_path")
|
|
@@ -993,6 +1281,10 @@ pub(super) fn agent_rollout_path(agent: &serde_json::Value) -> Option<RolloutPat
|
|
|
993
1281
|
.map(RolloutPath::new)
|
|
994
1282
|
}
|
|
995
1283
|
|
|
1284
|
+
/// ---
|
|
1285
|
+
/// purpose: 判断该席位的 resume 依据是否存在
|
|
1286
|
+
/// returns: 探测结果里的存在位
|
|
1287
|
+
/// ---
|
|
996
1288
|
pub(super) fn resume_backing_exists_for_agent(
|
|
997
1289
|
workspace: &Path,
|
|
998
1290
|
agent_id: &AgentId,
|
|
@@ -1032,6 +1324,13 @@ pub(crate) struct SessionIdentityProbeResult {
|
|
|
1032
1324
|
pub rollout_path: Option<PathBuf>,
|
|
1033
1325
|
}
|
|
1034
1326
|
|
|
1327
|
+
/// ---
|
|
1328
|
+
/// purpose: 探测 rollout 文件里嵌的席位身份是否与本席位一致
|
|
1329
|
+
/// params:
|
|
1330
|
+
/// _provider: 未参与判定
|
|
1331
|
+
/// rollout_path: 没有路径时三项都返回未知
|
|
1332
|
+
/// returns: 一致性判定、读到的嵌入席位名与实际探测路径;读不出嵌入身份时一致性为未知
|
|
1333
|
+
/// ---
|
|
1035
1334
|
pub(crate) fn session_identity_probe_for_agent(
|
|
1036
1335
|
agent_id: &AgentId,
|
|
1037
1336
|
_provider: Provider,
|
|
@@ -1056,6 +1355,10 @@ pub(crate) fn session_identity_probe_for_agent(
|
|
|
1056
1355
|
}
|
|
1057
1356
|
}
|
|
1058
1357
|
|
|
1358
|
+
/// ---
|
|
1359
|
+
/// purpose: 按 provider 分支探测 resume 依据,并记录所有查过的路径
|
|
1360
|
+
/// returns: 存在位与查过的路径列表;持久化的 rollout 路径即使不存在也记进列表
|
|
1361
|
+
/// ---
|
|
1059
1362
|
pub(super) fn resume_backing_probe_for_agent(
|
|
1060
1363
|
workspace: &Path,
|
|
1061
1364
|
agent_id: &AgentId,
|
|
@@ -1107,6 +1410,63 @@ pub(super) fn resume_backing_probe_for_agent(
|
|
|
1107
1410
|
}
|
|
1108
1411
|
copilot_session_store_has_session(session_id.as_str())
|
|
1109
1412
|
}
|
|
1413
|
+
Provider::Grok => {
|
|
1414
|
+
let spawn_cwd = agent
|
|
1415
|
+
.get("spawn_cwd")
|
|
1416
|
+
.and_then(serde_json::Value::as_str)
|
|
1417
|
+
.map(PathBuf::from)
|
|
1418
|
+
.unwrap_or_else(|| workspace.to_path_buf());
|
|
1419
|
+
match crate::provider::session_scan::grok::grok_session_dir(
|
|
1420
|
+
&spawn_cwd,
|
|
1421
|
+
session_id.as_str(),
|
|
1422
|
+
) {
|
|
1423
|
+
Some(dir) => {
|
|
1424
|
+
checked_paths.push(dir.clone());
|
|
1425
|
+
crate::provider::session_scan::grok::grok_session_archive_present(&dir)
|
|
1426
|
+
}
|
|
1427
|
+
None => false,
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
Provider::CursorAgent => {
|
|
1431
|
+
let spawn_cwd = agent
|
|
1432
|
+
.get("spawn_cwd")
|
|
1433
|
+
.and_then(serde_json::Value::as_str)
|
|
1434
|
+
.map(PathBuf::from)
|
|
1435
|
+
.unwrap_or_else(|| workspace.to_path_buf());
|
|
1436
|
+
let rollout_ok = rollout_path.is_some_and(|path| {
|
|
1437
|
+
crate::provider::session_scan::cursor::cursor_session_archive_present(path.as_path())
|
|
1438
|
+
});
|
|
1439
|
+
let discovered = crate::provider::session_scan::cursor::cursor_session_dir_for_cwd(
|
|
1440
|
+
session_id.as_str(),
|
|
1441
|
+
&spawn_cwd,
|
|
1442
|
+
);
|
|
1443
|
+
if let Some(dir) = discovered.as_ref() {
|
|
1444
|
+
checked_paths.push(dir.clone());
|
|
1445
|
+
}
|
|
1446
|
+
if let Some(home) = std::env::var_os("HOME").map(PathBuf::from) {
|
|
1447
|
+
checked_paths.push(home.join(".cursor").join("chats"));
|
|
1448
|
+
}
|
|
1449
|
+
rollout_ok
|
|
1450
|
+
|| discovered.as_ref().is_some_and(|dir| {
|
|
1451
|
+
crate::provider::session_scan::cursor::cursor_session_archive_present(dir)
|
|
1452
|
+
})
|
|
1453
|
+
}
|
|
1454
|
+
Provider::Pi => {
|
|
1455
|
+
let spawn_cwd = agent
|
|
1456
|
+
.get("spawn_cwd")
|
|
1457
|
+
.and_then(serde_json::Value::as_str)
|
|
1458
|
+
.filter(|value| !value.is_empty())
|
|
1459
|
+
.map(PathBuf::from)
|
|
1460
|
+
.unwrap_or_else(|| workspace.to_path_buf());
|
|
1461
|
+
rollout_path.is_some_and(|path| {
|
|
1462
|
+
crate::provider::session_scan::pi::validate_exact_backing(
|
|
1463
|
+
path.as_path(),
|
|
1464
|
+
session_id,
|
|
1465
|
+
&spawn_cwd,
|
|
1466
|
+
)
|
|
1467
|
+
.is_ok()
|
|
1468
|
+
})
|
|
1469
|
+
}
|
|
1110
1470
|
Provider::GeminiCli | Provider::Fake => false,
|
|
1111
1471
|
};
|
|
1112
1472
|
|
|
@@ -1121,10 +1481,20 @@ pub(super) fn resume_backing_probe_for_agent(
|
|
|
1121
1481
|
}
|
|
1122
1482
|
}
|
|
1123
1483
|
|
|
1484
|
+
/// ---
|
|
1485
|
+
/// purpose: 判断该 provider 是否支持 resume
|
|
1486
|
+
/// returns: 由 provider 适配器的能力位给出
|
|
1487
|
+
/// contract_id: lifecycle.common.provider_supports_resume
|
|
1488
|
+
/// ---
|
|
1124
1489
|
pub(super) fn provider_supports_resume(provider: Provider) -> bool {
|
|
1125
1490
|
crate::provider::get_adapter(provider).caps().resume
|
|
1126
1491
|
}
|
|
1127
1492
|
|
|
1493
|
+
/// ---
|
|
1494
|
+
/// purpose: 由 provider wire 名判断是否支持 resume
|
|
1495
|
+
/// returns: 名字不认识时为 false
|
|
1496
|
+
/// contract_id: lifecycle.common.provider_supports_resume
|
|
1497
|
+
/// ---
|
|
1128
1498
|
pub(super) fn provider_wire_supports_resume(provider: &str) -> bool {
|
|
1129
1499
|
parse_provider(provider)
|
|
1130
1500
|
.map(provider_supports_resume)
|
|
@@ -1300,6 +1670,13 @@ fn copilot_session_store_has_session(session_id: &str) -> bool {
|
|
|
1300
1670
|
.is_ok()
|
|
1301
1671
|
}
|
|
1302
1672
|
|
|
1673
|
+
/// ---
|
|
1674
|
+
/// purpose: 对缺会话的席位做一次捕获尝试
|
|
1675
|
+
/// params:
|
|
1676
|
+
/// state: 就地改写
|
|
1677
|
+
/// returns: 本次是否改动了 state
|
|
1678
|
+
/// errors: 捕获过程出错时返回 Provider
|
|
1679
|
+
/// ---
|
|
1303
1680
|
pub(crate) fn refresh_missing_provider_sessions(
|
|
1304
1681
|
state: &mut serde_json::Value,
|
|
1305
1682
|
) -> Result<bool, LifecycleError> {
|
|
@@ -1313,6 +1690,15 @@ pub(crate) fn refresh_missing_provider_sessions(
|
|
|
1313
1690
|
.map_err(|e| LifecycleError::Provider(e.to_string()))
|
|
1314
1691
|
}
|
|
1315
1692
|
|
|
1693
|
+
/// ---
|
|
1694
|
+
/// purpose: 有界等待缺会话席位收敛,并逐轮写进度事件
|
|
1695
|
+
/// params:
|
|
1696
|
+
/// deadline: 等待上限
|
|
1697
|
+
/// poll_interval: 轮询间隔
|
|
1698
|
+
/// allow_fresh: 只写进事件载荷,不改变收敛判定
|
|
1699
|
+
/// returns: 收敛结论
|
|
1700
|
+
/// errors: 收敛过程出错时返回 StatePersist
|
|
1701
|
+
/// ---
|
|
1316
1702
|
pub(crate) fn converge_missing_provider_sessions(
|
|
1317
1703
|
state: &mut serde_json::Value,
|
|
1318
1704
|
deadline: std::time::Duration,
|
|
@@ -1358,6 +1744,10 @@ fn write_session_convergence_progress_event(
|
|
|
1358
1744
|
.map_err(|e| e.to_string())
|
|
1359
1745
|
}
|
|
1360
1746
|
|
|
1747
|
+
/// ---
|
|
1748
|
+
/// purpose: 列出必须等到会话收敛才能 restart 的席位
|
|
1749
|
+
/// returns: 排序后的席位 id;只保留无会话、状态为 running 且确有待保留上下文的席位,从未捕获过的席位不进入
|
|
1750
|
+
/// ---
|
|
1361
1751
|
pub(crate) fn restart_required_missing_session_agent_ids(state: &serde_json::Value) -> Vec<String> {
|
|
1362
1752
|
let mut missing = crate::session_capture::incomplete_resumable_agent_ids(state)
|
|
1363
1753
|
.into_iter()
|
|
@@ -1397,6 +1787,10 @@ pub(crate) fn restart_required_missing_session_agent_ids(state: &serde_json::Val
|
|
|
1397
1787
|
missing.sort();
|
|
1398
1788
|
missing
|
|
1399
1789
|
}
|
|
1790
|
+
/// ---
|
|
1791
|
+
/// purpose: 取该席位的窗口名
|
|
1792
|
+
/// returns: 席位行里的非空 window,缺失时用席位 id
|
|
1793
|
+
/// ---
|
|
1400
1794
|
pub(super) fn agent_window(agent: &serde_json::Value, agent_id: &AgentId) -> String {
|
|
1401
1795
|
agent
|
|
1402
1796
|
.get("window")
|
|
@@ -1408,6 +1802,10 @@ pub(super) fn agent_window(agent: &serde_json::Value, agent_id: &AgentId) -> Str
|
|
|
1408
1802
|
|
|
1409
1803
|
pub(super) use crate::provider::wire::{parse_provider, provider_wire};
|
|
1410
1804
|
|
|
1805
|
+
/// ---
|
|
1806
|
+
/// purpose: 把 auth_mode 字符串解析成枚举
|
|
1807
|
+
/// returns: 只认三种取值,未知返回 None
|
|
1808
|
+
/// ---
|
|
1411
1809
|
pub(super) fn parse_auth_mode(raw: &str) -> Option<AuthMode> {
|
|
1412
1810
|
match raw {
|
|
1413
1811
|
"subscription" => Some(AuthMode::Subscription),
|
|
@@ -1417,6 +1815,11 @@ pub(super) fn parse_auth_mode(raw: &str) -> Option<AuthMode> {
|
|
|
1417
1815
|
}
|
|
1418
1816
|
}
|
|
1419
1817
|
|
|
1818
|
+
/// ---
|
|
1819
|
+
/// purpose: 从给定目录读出 team spec
|
|
1820
|
+
/// returns: 解析后的 YAML
|
|
1821
|
+
/// errors: 文件不存在返回 TeamSelect,读文件或解析失败返回 Compile
|
|
1822
|
+
/// ---
|
|
1420
1823
|
pub(super) fn load_team_spec(workspace: &Path) -> Result<YamlValue, LifecycleError> {
|
|
1421
1824
|
let spec_path = workspace.join("team.spec.yaml");
|
|
1422
1825
|
if !spec_path.exists() {
|
|
@@ -1430,6 +1833,10 @@ pub(super) fn load_team_spec(workspace: &Path) -> Result<YamlValue, LifecycleErr
|
|
|
1430
1833
|
yaml::loads(&text).map_err(|e| LifecycleError::Compile(e.to_string()))
|
|
1431
1834
|
}
|
|
1432
1835
|
|
|
1836
|
+
/// ---
|
|
1837
|
+
/// purpose: 在 spec 的 agents 列表里找该席位
|
|
1838
|
+
/// returns: 命中的节点;该 id 其实是 leader 时返回 None
|
|
1839
|
+
/// ---
|
|
1433
1840
|
pub(super) fn find_spec_agent<'a>(
|
|
1434
1841
|
spec: &'a YamlValue,
|
|
1435
1842
|
agent_id: &AgentId,
|
|
@@ -1452,10 +1859,18 @@ pub(super) fn find_spec_agent<'a>(
|
|
|
1452
1859
|
})
|
|
1453
1860
|
}
|
|
1454
1861
|
|
|
1862
|
+
/// ---
|
|
1863
|
+
/// purpose: 构造未知席位的错误
|
|
1864
|
+
/// returns: 带席位 id 的 RequirementUnmet
|
|
1865
|
+
/// ---
|
|
1455
1866
|
pub(super) fn unknown_worker(agent_id: &AgentId) -> LifecycleError {
|
|
1456
1867
|
LifecycleError::RequirementUnmet(format!("unknown worker agent id: {agent_id}"))
|
|
1457
1868
|
}
|
|
1458
1869
|
|
|
1870
|
+
/// ---
|
|
1871
|
+
/// purpose: 定出 session 名,state 缺失时回落到 spec
|
|
1872
|
+
/// returns: 依次取 state 的 session_name、spec 的 runtime.session_name、由 team 名派生,最后用默认名
|
|
1873
|
+
/// ---
|
|
1459
1874
|
pub(super) fn state_session_name_from_spec(
|
|
1460
1875
|
state: &serde_json::Value,
|
|
1461
1876
|
spec: &YamlValue,
|
|
@@ -1480,6 +1895,14 @@ pub(super) fn state_session_name_from_spec(
|
|
|
1480
1895
|
.unwrap_or_else(|| SessionName::new("team-agent"))
|
|
1481
1896
|
}
|
|
1482
1897
|
|
|
1898
|
+
/// ---
|
|
1899
|
+
/// purpose: 把席位标记为已停并清掉 window 与 pane_id 字段(pane_pid 等其余字段保留)
|
|
1900
|
+
/// params:
|
|
1901
|
+
/// state: 就地改写,非对象时先重置成空对象
|
|
1902
|
+
/// spec_agent: 提供 provider 等最小投影
|
|
1903
|
+
/// returns: 成功返回空值
|
|
1904
|
+
/// errors: state 结构不是对象时返回 StatePersist
|
|
1905
|
+
/// ---
|
|
1483
1906
|
pub(super) fn mark_agent_stopped(
|
|
1484
1907
|
state: &mut serde_json::Value,
|
|
1485
1908
|
agent_id: &AgentId,
|
|
@@ -1529,6 +1952,13 @@ pub(super) fn mark_agent_stopped(
|
|
|
1529
1952
|
Ok(())
|
|
1530
1953
|
}
|
|
1531
1954
|
|
|
1955
|
+
/// ---
|
|
1956
|
+
/// purpose: 窗口本就存在而未新起进程时,把席位标记为运行中
|
|
1957
|
+
/// params:
|
|
1958
|
+
/// pane: 探到的现有 pane,用于写 pane id 与进程号
|
|
1959
|
+
/// returns: 成功返回空值
|
|
1960
|
+
/// errors: state 结构不是对象时返回 StatePersist
|
|
1961
|
+
/// ---
|
|
1532
1962
|
pub(super) fn mark_agent_running_noop(
|
|
1533
1963
|
state: &mut serde_json::Value,
|
|
1534
1964
|
agent_id: &AgentId,
|
|
@@ -1587,6 +2017,11 @@ pub(super) fn mark_agent_running_noop(
|
|
|
1587
2017
|
Ok(())
|
|
1588
2018
|
}
|
|
1589
2019
|
|
|
2020
|
+
/// ---
|
|
2021
|
+
/// purpose: 写一条起席无操作事件
|
|
2022
|
+
/// returns: 成功返回空值
|
|
2023
|
+
/// errors: 事件写入失败时返回 StatePersist
|
|
2024
|
+
/// ---
|
|
1590
2025
|
pub(super) fn write_start_agent_noop_event(
|
|
1591
2026
|
workspace: &Path,
|
|
1592
2027
|
agent_id: &AgentId,
|
|
@@ -1606,6 +2041,10 @@ pub(super) fn write_start_agent_noop_event(
|
|
|
1606
2041
|
Ok(())
|
|
1607
2042
|
}
|
|
1608
2043
|
|
|
2044
|
+
/// ---
|
|
2045
|
+
/// purpose: 判断某 session 里是否存在该窗口
|
|
2046
|
+
/// returns: 明确列出该窗口才为 true;列窗口出错或 panic 时为 false
|
|
2047
|
+
/// ---
|
|
1609
2048
|
pub(super) fn window_exists(
|
|
1610
2049
|
transport: &dyn crate::transport::Transport,
|
|
1611
2050
|
session_name: &SessionName,
|
|
@@ -1619,6 +2058,11 @@ pub(super) fn window_exists(
|
|
|
1619
2058
|
}
|
|
1620
2059
|
}
|
|
1621
2060
|
|
|
2061
|
+
/// ---
|
|
2062
|
+
/// purpose: 在 state 里把该席位的显示标记为已关
|
|
2063
|
+
/// params:
|
|
2064
|
+
/// state: 就地改写;只有 ghostty 工作区后端会被改写状态与标题,其余后端不动
|
|
2065
|
+
/// ---
|
|
1622
2066
|
pub(super) fn close_agent_display(state: &mut serde_json::Value, agent_id: &AgentId) {
|
|
1623
2067
|
let Some(display) = state
|
|
1624
2068
|
.get_mut("agents")
|
|
@@ -1646,6 +2090,13 @@ pub(super) fn close_agent_display(state: &mut serde_json::Value, agent_id: &Agen
|
|
|
1646
2090
|
}
|
|
1647
2091
|
}
|
|
1648
2092
|
|
|
2093
|
+
/// ---
|
|
2094
|
+
/// purpose: 丢弃该席位的会话捕获字段并标记为已停
|
|
2095
|
+
/// params:
|
|
2096
|
+
/// state: 就地改写;只删会话捕获相关字段,工作目录等状态字段保留
|
|
2097
|
+
/// returns: 成功返回空值
|
|
2098
|
+
/// errors: 席位不存在返回 RequirementUnmet,席位行不是对象返回 StatePersist
|
|
2099
|
+
/// ---
|
|
1649
2100
|
pub(super) fn discard_agent_session_fields(
|
|
1650
2101
|
state: &mut serde_json::Value,
|
|
1651
2102
|
agent_id: &AgentId,
|
|
@@ -1693,6 +2144,10 @@ pub(super) fn discard_agent_session_fields(
|
|
|
1693
2144
|
Ok(())
|
|
1694
2145
|
}
|
|
1695
2146
|
|
|
2147
|
+
/// ---
|
|
2148
|
+
/// purpose: 判断该席位是否在运行
|
|
2149
|
+
/// returns: 状态为 running 或 busy 直接为真;其余状态都退到按 session 与窗口存在性判定
|
|
2150
|
+
/// ---
|
|
1696
2151
|
pub(super) fn agent_is_running(
|
|
1697
2152
|
state: &serde_json::Value,
|
|
1698
2153
|
agent_id: &AgentId,
|
|
@@ -1724,6 +2179,10 @@ pub(super) fn agent_is_running(
|
|
|
1724
2179
|
window_exists(transport, &session_name, window)
|
|
1725
2180
|
}
|
|
1726
2181
|
|
|
2182
|
+
/// ---
|
|
2183
|
+
/// purpose: 判断该席位是不是动态生成的
|
|
2184
|
+
/// returns: state 里记了动态角色文件,或 spec 里标了来源席位时为 true
|
|
2185
|
+
/// ---
|
|
1727
2186
|
pub(super) fn is_dynamic_agent(
|
|
1728
2187
|
state: &serde_json::Value,
|
|
1729
2188
|
spec_agent: &YamlValue,
|
|
@@ -1742,6 +2201,13 @@ pub(super) fn is_dynamic_agent(
|
|
|
1742
2201
|
.is_some_and(|s| !s.is_empty())
|
|
1743
2202
|
}
|
|
1744
2203
|
|
|
2204
|
+
/// ---
|
|
2205
|
+
/// purpose: 在 spawn 之前预判 tmux 的起法,供审计事件记录
|
|
2206
|
+
/// params:
|
|
2207
|
+
/// layout_placement: 有布局位置时按是否起新窗口区分
|
|
2208
|
+
/// into_existing_session: 目标 session 已存在
|
|
2209
|
+
/// returns: new-session、new-window 或 split-window
|
|
2210
|
+
/// ---
|
|
1745
2211
|
/// 0.4.6 Stage 2: predict the tmux start mode BEFORE the spawn call, so
|
|
1746
2212
|
/// the `provider.worker.spawn_argv` event can record what the spawn will
|
|
1747
2213
|
/// actually do. Same logic as `tmux_start_mode_for_spawn` in agent.rs
|
|
@@ -1768,6 +2234,10 @@ pub(super) fn predict_tmux_start_mode(
|
|
|
1768
2234
|
}
|
|
1769
2235
|
}
|
|
1770
2236
|
|
|
2237
|
+
/// ---
|
|
2238
|
+
/// purpose: 从盘上读该席位当前的 spawn 世代号
|
|
2239
|
+
/// returns: 读到的值;读不出 state 或字段缺失时为 0
|
|
2240
|
+
/// ---
|
|
1771
2241
|
/// 0.4.6 Stage 2: read state.agents[agent_id].spawn_epoch from disk to
|
|
1772
2242
|
/// stamp the spawn_argv event with the current cohort identifier. Returns
|
|
1773
2243
|
/// 0 if the agent row / field is missing.
|
|
@@ -1783,6 +2253,12 @@ pub(super) fn state_spawn_epoch_for_agent(workspace: &Path, agent_id: &AgentId)
|
|
|
1783
2253
|
.unwrap_or(0)
|
|
1784
2254
|
}
|
|
1785
2255
|
|
|
2256
|
+
/// ---
|
|
2257
|
+
/// purpose: 新起进程后清掉该席位的活动观测字段
|
|
2258
|
+
/// params:
|
|
2259
|
+
/// agent: 就地删除观测字段;生命周期与拓扑字段一概不动
|
|
2260
|
+
/// returns: 清完之后观测缺失表示未知,不等于空闲
|
|
2261
|
+
/// ---
|
|
1786
2262
|
/// 0.5.32 (`.team/artifacts/restart-resumed-stale-activity-locate.md` §5):
|
|
1787
2263
|
/// clear the per-agent turn/activity observation set on a successful new
|
|
1788
2264
|
/// worker process cohort. Called from `mark_agent_started` /
|