@team-agent/installer 0.5.65 → 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 +6 -0
- package/crates/team-agent/src/cli/adapters.rs +6 -1
- 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/compile.rs +1 -1
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
- package/crates/team-agent/src/cli/tests/mod.rs +2 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -1
- 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/cli/tests/verb_validate.rs +1 -1
- package/crates/team-agent/src/communication_mode/mod.rs +6 -4
- package/crates/team-agent/src/compiler/tests.rs +23 -20
- package/crates/team-agent/src/compiler.rs +84 -5
- package/crates/team-agent/src/conpty/backend.rs +16 -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 +1080 -58
- package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1 -1
- 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 -14
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +69 -2
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +196 -2
- 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 -457
- 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 +101 -4
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +244 -48
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -2
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
- package/crates/team-agent/src/lifecycle/launch.rs +209 -43
- 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 +213 -3
- package/crates/team-agent/src/lifecycle/restart/common.rs +373 -18
- 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 +165 -15
- 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/acceptance_b_batch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +3 -4
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +4 -4
- 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 +159 -174
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +15 -13
- package/crates/team-agent/src/lifecycle/tests/core.rs +4 -18
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +1 -1
- 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/display_adaptive_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +1 -1
- 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/harvest2_a_batch_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +3 -3
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +65 -40
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +20 -32
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +17 -9
- 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 +24 -23
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +98 -62
- package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -1
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +41 -8
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +3 -3
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +60 -2
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
- package/crates/team-agent/src/lifecycle/tests.rs +23 -15
- package/crates/team-agent/src/lifecycle/types.rs +61 -2
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +200 -33
- 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/main_preserved.rs +1 -1
- 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/model/spec.rs +129 -6
- package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +3 -1
- package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +3 -1
- package/crates/team-agent/src/model/testdata/team.spec.yaml +3 -1
- 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/claude.rs +5 -2
- package/crates/team-agent/src/provider/adapters/codex.rs +5 -2
- package/crates/team-agent/src/provider/adapters/copilot.rs +5 -2
- 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 +124 -0
- 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 +13 -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 +1083 -75
- package/crates/team-agent/src/transport/test_support.rs +20 -0
- package/crates/team-agent/src/transport/tests/wire.rs +28 -2
- package/crates/team-agent/src/transport.rs +265 -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/approval.rs +0 -134
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -483
- package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
- package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +0 -1187
- package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +0 -327
|
@@ -1,6 +1,30 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: fork 一席的入口,解析活跃 team 并绑定它实际使用的 tmux socket
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: fork_agent
|
|
6
|
+
//! what: 解析 team 与 transport 后转 fork_agent_with_transport
|
|
7
|
+
//! depends:
|
|
8
|
+
//! - crate::state::selector
|
|
9
|
+
//! - crate::lifecycle::restart
|
|
10
|
+
//! - crate::tmux_backend
|
|
11
|
+
//! boundary:
|
|
12
|
+
//! - 不自己做窗口注入,注入在 fork_agent.rs
|
|
13
|
+
//! - 不用 workspace 哈希兜底 socket 覆盖已持久化的 endpoint
|
|
14
|
+
//! maturity: wired
|
|
15
|
+
//! ---
|
|
1
16
|
//!
|
|
2
17
|
use super::*;
|
|
3
18
|
|
|
19
|
+
/// ---
|
|
20
|
+
/// purpose: fork 一席的对外入口
|
|
21
|
+
/// params:
|
|
22
|
+
/// source_agent_id: 源席位
|
|
23
|
+
/// as_agent_id: 新席位名
|
|
24
|
+
/// label: 新席位的角色标签
|
|
25
|
+
/// returns: fork 报告
|
|
26
|
+
/// errors: 选不到活跃 team 返回 TeamSelect,其余透传 fork_agent_with_transport
|
|
27
|
+
/// ---
|
|
4
28
|
/// `fork_agent(workspace, source_agent_id, as_agent_id, ...)`(`lifecycle/operations.py:284`)。
|
|
5
29
|
/// native session fork(provider 须 supports_session_fork ∧ auth_mode!=compatible_api);
|
|
6
30
|
/// 失败回滚,每条失败臂 `adapter.cleanup_mcp`。
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 判定 .grok/config.toml env 里哪些键是每席私有
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: is_per_seat_env_key
|
|
6
|
+
//! what: TEAM_AGENT_* 除 WORKSPACE 外都是每席键
|
|
7
|
+
//! - name: per_seat_keys_in_toml
|
|
8
|
+
//! what: 扫描 toml env 表里的每席键
|
|
9
|
+
//! - name: strip_per_seat_keys_from_toml
|
|
10
|
+
//! what: 从任意 .env 表删掉每席键,留下非框架键
|
|
11
|
+
//! - name: non_per_seat_env_in_tables
|
|
12
|
+
//! what: 从指定表的 .env 取出非每席键(值原样),供 overlay 保留
|
|
13
|
+
//! boundary:
|
|
14
|
+
//! - 不写盘
|
|
15
|
+
//! - 不数 grok 席位个数
|
|
16
|
+
//! - 剥离结果只含键名,不含键值
|
|
17
|
+
//! maturity: wired
|
|
18
|
+
//! ---
|
|
19
|
+
|
|
20
|
+
/// ---
|
|
21
|
+
/// purpose: 判断某环境键是否属于每席私有
|
|
22
|
+
/// returns: 以 TEAM_AGENT_ 起头且不是 TEAM_AGENT_WORKSPACE 时为 true
|
|
23
|
+
/// ---
|
|
24
|
+
/// Shared identity keys belong on pane env. The only TEAM_AGENT_* key
|
|
25
|
+
/// allowed in the directory-scoped grok toml is WORKSPACE (same cwd).
|
|
26
|
+
pub(crate) fn is_per_seat_env_key(key: &str) -> bool {
|
|
27
|
+
let key = key.trim();
|
|
28
|
+
key.starts_with("TEAM_AGENT_") && key != "TEAM_AGENT_WORKSPACE"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// ---
|
|
32
|
+
/// purpose: 扫出 toml 里所有 env 表中的每席键
|
|
33
|
+
/// returns: 首次出现顺序的键值对,值已去掉引号
|
|
34
|
+
/// ---
|
|
35
|
+
pub(crate) fn per_seat_keys_in_toml(text: &str) -> Vec<(String, String)> {
|
|
36
|
+
let mut in_env_table = false;
|
|
37
|
+
let mut out = Vec::new();
|
|
38
|
+
for line in text.lines() {
|
|
39
|
+
let trimmed = line.trim();
|
|
40
|
+
if trimmed.starts_with('[') && trimmed.ends_with(']') {
|
|
41
|
+
let name = &trimmed[1..trimmed.len() - 1];
|
|
42
|
+
in_env_table = name.ends_with(".env");
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if !in_env_table {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
let Some((key, rest)) = trimmed.split_once('=') else {
|
|
49
|
+
continue;
|
|
50
|
+
};
|
|
51
|
+
let key = key.trim();
|
|
52
|
+
if !is_per_seat_env_key(key) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
let rest = rest.trim();
|
|
56
|
+
let value = rest
|
|
57
|
+
.strip_prefix('"')
|
|
58
|
+
.and_then(|s| s.strip_suffix('"'))
|
|
59
|
+
.or_else(|| rest.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
|
|
60
|
+
.unwrap_or(rest);
|
|
61
|
+
if out.iter().any(|(existing, _)| existing == key) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
out.push((key.to_string(), value.to_string()));
|
|
65
|
+
}
|
|
66
|
+
out
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// ---
|
|
70
|
+
/// purpose: 从所有 env 表里删掉每席键
|
|
71
|
+
/// returns: 删后的全文,以及被删键名的首次出现顺序列表,不含值
|
|
72
|
+
/// ---
|
|
73
|
+
/// Drop per-seat `TEAM_AGENT_*` assignments from any `*.env` table.
|
|
74
|
+
/// Other keys (`TEAM_AGENT_WORKSPACE`, `GROK_FOLDER_TRUST`, user keys) stay.
|
|
75
|
+
/// Returned names are first-seen order, values never included.
|
|
76
|
+
pub(crate) fn strip_per_seat_keys_from_toml(text: &str) -> (String, Vec<String>) {
|
|
77
|
+
let mut in_env_table = false;
|
|
78
|
+
let mut out = String::new();
|
|
79
|
+
let mut removed = Vec::new();
|
|
80
|
+
for line in text.lines() {
|
|
81
|
+
let trimmed = line.trim();
|
|
82
|
+
if trimmed.starts_with('[') && trimmed.ends_with(']') {
|
|
83
|
+
let name = &trimmed[1..trimmed.len() - 1];
|
|
84
|
+
in_env_table = name.ends_with(".env");
|
|
85
|
+
out.push_str(line);
|
|
86
|
+
out.push('\n');
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if in_env_table {
|
|
90
|
+
if let Some((key, _)) = trimmed.split_once('=') {
|
|
91
|
+
let key = key.trim();
|
|
92
|
+
if is_per_seat_env_key(key) {
|
|
93
|
+
if !removed.iter().any(|existing| existing == key) {
|
|
94
|
+
removed.push(key.to_string());
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
out.push_str(line);
|
|
101
|
+
out.push('\n');
|
|
102
|
+
}
|
|
103
|
+
(out, removed)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn toml_env_value(rest: &str) -> String {
|
|
107
|
+
let rest = rest.trim();
|
|
108
|
+
rest.strip_prefix('"')
|
|
109
|
+
.and_then(|s| s.strip_suffix('"'))
|
|
110
|
+
.or_else(|| rest.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
|
|
111
|
+
.unwrap_or(rest)
|
|
112
|
+
.to_string()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// ---
|
|
116
|
+
/// purpose: 取出指定表的 env 段里非每席的键值
|
|
117
|
+
/// params:
|
|
118
|
+
/// tables: 目标表名前缀,只看这些表下的 env 段
|
|
119
|
+
/// returns: 键到值的有序映射,供覆盖写入时保留用户键
|
|
120
|
+
/// ---
|
|
121
|
+
/// Keys in `tables*.env` that are **not** per-seat. Unknown keys must be
|
|
122
|
+
/// kept — detection failure does not authorize deletion.
|
|
123
|
+
pub(crate) fn non_per_seat_env_in_tables(
|
|
124
|
+
text: &str,
|
|
125
|
+
tables: &[&str],
|
|
126
|
+
) -> std::collections::BTreeMap<String, String> {
|
|
127
|
+
let mut in_target_env = false;
|
|
128
|
+
let mut out = std::collections::BTreeMap::new();
|
|
129
|
+
for line in text.lines() {
|
|
130
|
+
let trimmed = line.trim();
|
|
131
|
+
if trimmed.starts_with('[') && trimmed.ends_with(']') {
|
|
132
|
+
let name = &trimmed[1..trimmed.len() - 1];
|
|
133
|
+
in_target_env = name.ends_with(".env")
|
|
134
|
+
&& tables
|
|
135
|
+
.iter()
|
|
136
|
+
.any(|table| name == *table || name.starts_with(&format!("{table}.")));
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if !in_target_env {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
let Some((key, rest)) = trimmed.split_once('=') else {
|
|
143
|
+
continue;
|
|
144
|
+
};
|
|
145
|
+
let key = key.trim();
|
|
146
|
+
if key.is_empty() || is_per_seat_env_key(key) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
out.entry(key.to_string())
|
|
150
|
+
.or_insert_with(|| toml_env_value(rest));
|
|
151
|
+
}
|
|
152
|
+
out
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#[cfg(test)]
|
|
156
|
+
mod tests {
|
|
157
|
+
use super::*;
|
|
158
|
+
|
|
159
|
+
#[test]
|
|
160
|
+
fn workspace_is_shared_other_team_agent_keys_are_per_seat() {
|
|
161
|
+
assert!(!is_per_seat_env_key("TEAM_AGENT_WORKSPACE"));
|
|
162
|
+
assert!(is_per_seat_env_key("TEAM_AGENT_ID"));
|
|
163
|
+
assert!(is_per_seat_env_key("TEAM_AGENT_OWNER_TEAM_ID"));
|
|
164
|
+
assert!(is_per_seat_env_key("TEAM_AGENT_AUTH_MODE"));
|
|
165
|
+
assert!(is_per_seat_env_key("TEAM_AGENT_FUTURE_SEAT_KEY"));
|
|
166
|
+
assert!(!is_per_seat_env_key("GROK_FOLDER_TRUST"));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[test]
|
|
170
|
+
fn scan_finds_per_seat_keys_in_env_table_only() {
|
|
171
|
+
let text = r#"
|
|
172
|
+
[mcp_servers.other.env]
|
|
173
|
+
TEAM_AGENT_ID = "ignore-other-server-still-counts"
|
|
174
|
+
|
|
175
|
+
[mcp_servers.team_orchestrator]
|
|
176
|
+
command = "/bin/x"
|
|
177
|
+
|
|
178
|
+
[mcp_servers.team_orchestrator.env]
|
|
179
|
+
TEAM_AGENT_WORKSPACE = "/ws"
|
|
180
|
+
TEAM_AGENT_AUTH_MODE = "impostor"
|
|
181
|
+
"#;
|
|
182
|
+
let keys = per_seat_keys_in_toml(text);
|
|
183
|
+
assert!(
|
|
184
|
+
keys.iter()
|
|
185
|
+
.any(|(k, v)| k == "TEAM_AGENT_AUTH_MODE" && v == "impostor"),
|
|
186
|
+
"must see AUTH; keys={keys:?}"
|
|
187
|
+
);
|
|
188
|
+
assert!(
|
|
189
|
+
keys.iter().any(|(k, _)| k == "TEAM_AGENT_ID"),
|
|
190
|
+
"any env table per-seat key counts; keys={keys:?}"
|
|
191
|
+
);
|
|
192
|
+
assert!(
|
|
193
|
+
!keys.iter().any(|(k, _)| k == "TEAM_AGENT_WORKSPACE"),
|
|
194
|
+
"WORKSPACE is shared; keys={keys:?}"
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#[test]
|
|
199
|
+
fn strip_drops_only_per_seat_keys_and_returns_names() {
|
|
200
|
+
let text = r#"
|
|
201
|
+
[mcp_servers.keep.env]
|
|
202
|
+
GROK_FOLDER_TRUST = "1"
|
|
203
|
+
TEAM_AGENT_ID = "impostor"
|
|
204
|
+
TEAM_AGENT_WORKSPACE = "/ws"
|
|
205
|
+
|
|
206
|
+
[mcp_servers.keep]
|
|
207
|
+
command = "/bin/keep"
|
|
208
|
+
|
|
209
|
+
[other]
|
|
210
|
+
TEAM_AGENT_AUTH_MODE = "not-in-env-table"
|
|
211
|
+
"#;
|
|
212
|
+
let (cleaned, removed) = strip_per_seat_keys_from_toml(text);
|
|
213
|
+
assert_eq!(removed, vec!["TEAM_AGENT_ID".to_string()]);
|
|
214
|
+
assert!(
|
|
215
|
+
!cleaned.contains("TEAM_AGENT_ID"),
|
|
216
|
+
"per-seat key must leave the env table; cleaned={cleaned}"
|
|
217
|
+
);
|
|
218
|
+
assert!(
|
|
219
|
+
cleaned.contains("GROK_FOLDER_TRUST = \"1\"")
|
|
220
|
+
&& cleaned.contains("TEAM_AGENT_WORKSPACE = \"/ws\"")
|
|
221
|
+
&& cleaned.contains("TEAM_AGENT_AUTH_MODE = \"not-in-env-table\"")
|
|
222
|
+
&& cleaned.contains("command = \"/bin/keep\""),
|
|
223
|
+
"non-per-seat keys and non-env tables stay; cleaned={cleaned}"
|
|
224
|
+
);
|
|
225
|
+
assert!(
|
|
226
|
+
!cleaned.contains("impostor"),
|
|
227
|
+
"stripped values must not be rewritten; cleaned={cleaned}"
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#[test]
|
|
232
|
+
fn non_per_seat_scan_keeps_unknown_keys_from_named_tables_only() {
|
|
233
|
+
let text = r#"
|
|
234
|
+
[mcp_servers.keep-me.env]
|
|
235
|
+
GROK_FOLDER_TRUST = "other-table"
|
|
236
|
+
|
|
237
|
+
[mcp_servers.team_orchestrator.env]
|
|
238
|
+
TEAM_AGENT_ID = "stale"
|
|
239
|
+
GROK_FOLDER_TRUST = "1"
|
|
240
|
+
TEAM_AGENT_WORKSPACE = "/ws"
|
|
241
|
+
USER_EXTRA = "keep"
|
|
242
|
+
"#;
|
|
243
|
+
let kept = non_per_seat_env_in_tables(text, &["mcp_servers.team_orchestrator"]);
|
|
244
|
+
assert_eq!(kept.get("GROK_FOLDER_TRUST").map(String::as_str), Some("1"));
|
|
245
|
+
assert_eq!(kept.get("USER_EXTRA").map(String::as_str), Some("keep"));
|
|
246
|
+
assert_eq!(
|
|
247
|
+
kept.get("TEAM_AGENT_WORKSPACE").map(String::as_str),
|
|
248
|
+
Some("/ws")
|
|
249
|
+
);
|
|
250
|
+
assert!(
|
|
251
|
+
!kept.contains_key("TEAM_AGENT_ID"),
|
|
252
|
+
"per-seat keys are not preserved; kept={kept:?}"
|
|
253
|
+
);
|
|
254
|
+
assert_ne!(
|
|
255
|
+
kept.get("GROK_FOLDER_TRUST").map(String::as_str),
|
|
256
|
+
Some("other-table"),
|
|
257
|
+
"only the named table is read; kept={kept:?}"
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: team 与席位的身份取值,团队键、display 后端、auth_mode、effort 与 quick-start 层级判定
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: spec_team_id
|
|
6
|
+
//! what: 从 spec 里取 team 标识
|
|
7
|
+
//! - name: runtime_team_key_for_spec
|
|
8
|
+
//! what: 由 spec 路径与 session 名算出 runtime 团队键
|
|
9
|
+
//! - name: transport_has_session
|
|
10
|
+
//! what: 探测 tmux session 是否存在,探测本身出错一律当不存在
|
|
11
|
+
//! - name: provider_effort_from_raw
|
|
12
|
+
//! what: 解析 effort 并要求该 provider 支持,否则 None
|
|
13
|
+
//! - name: quick_start_depth_guard
|
|
14
|
+
//! what: 定出本次 quick-start 的父团队与层级,推不出父团队时拒绝
|
|
15
|
+
//! - name: annotate_persisted_team_depth
|
|
16
|
+
//! what: 把父团队与层级写回已落盘的 runtime state
|
|
17
|
+
//! depends:
|
|
18
|
+
//! - crate::state::projection
|
|
19
|
+
//! - crate::state::persist
|
|
20
|
+
//! - crate::state::repository
|
|
21
|
+
//! - crate::lifecycle::display
|
|
22
|
+
//! - crate::provider::wire
|
|
23
|
+
//! boundary:
|
|
24
|
+
//! - 只做身份判定与取值,不 spawn、不开显示
|
|
25
|
+
//! - effort 不被支持时只丢弃并给出事件载荷,不改写成别的档位
|
|
26
|
+
//! - 层级判定拿不准时报错,不默认当顶层团队
|
|
27
|
+
//! maturity: wired
|
|
28
|
+
//! ---
|
|
1
29
|
use std::collections::{BTreeMap, BTreeSet};
|
|
2
30
|
use std::path::{Path, PathBuf};
|
|
3
31
|
use std::process::Command;
|
|
@@ -14,6 +42,12 @@ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest}
|
|
|
14
42
|
|
|
15
43
|
use super::*;
|
|
16
44
|
|
|
45
|
+
/// ---
|
|
46
|
+
/// purpose: 从 spec 里取 team 标识
|
|
47
|
+
/// params:
|
|
48
|
+
/// spec: 已解析的 spec
|
|
49
|
+
/// returns: team.id 优先,其次 team.name,再次顶层 name;都没有则 None
|
|
50
|
+
/// ---
|
|
17
51
|
pub(super) fn spec_team_id(spec: &Value) -> Option<String> {
|
|
18
52
|
spec.get("team")
|
|
19
53
|
.and_then(|v| v.get("id").or_else(|| v.get("name")))
|
|
@@ -22,6 +56,10 @@ pub(super) fn spec_team_id(spec: &Value) -> Option<String> {
|
|
|
22
56
|
.or_else(|| spec.get("name").and_then(Value::as_str).map(str::to_string))
|
|
23
57
|
}
|
|
24
58
|
|
|
59
|
+
/// ---
|
|
60
|
+
/// purpose: 取 runtime state 里显式记录的当前活跃团队键
|
|
61
|
+
/// returns: 非空的 active_team_key,缺失或为空则 None
|
|
62
|
+
/// ---
|
|
25
63
|
pub(super) fn explicit_active_team_key(state: &serde_json::Value) -> Option<String> {
|
|
26
64
|
state
|
|
27
65
|
.get("active_team_key")
|
|
@@ -30,6 +68,10 @@ pub(super) fn explicit_active_team_key(state: &serde_json::Value) -> Option<Stri
|
|
|
30
68
|
.map(str::to_string)
|
|
31
69
|
}
|
|
32
70
|
|
|
71
|
+
/// ---
|
|
72
|
+
/// purpose: 由 spec 路径、spec 内容与 session 名拼出 runtime 团队键
|
|
73
|
+
/// returns: 交给 state::projection 计算出的团队键
|
|
74
|
+
/// ---
|
|
33
75
|
pub(super) fn runtime_team_key_for_spec(
|
|
34
76
|
spec_path: &Path,
|
|
35
77
|
spec: &Value,
|
|
@@ -45,6 +87,10 @@ pub(super) fn runtime_team_key_for_spec(
|
|
|
45
87
|
crate::state::projection::team_state_key(&state)
|
|
46
88
|
}
|
|
47
89
|
|
|
90
|
+
/// ---
|
|
91
|
+
/// purpose: 探测某 tmux session 是否存在
|
|
92
|
+
/// returns: 只有确定存在才返回 true;transport 报错或 panic 一律当作不存在
|
|
93
|
+
/// ---
|
|
48
94
|
pub(super) fn transport_has_session(transport: &dyn Transport, session_name: &SessionName) -> bool {
|
|
49
95
|
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
|
50
96
|
transport.has_session(session_name)
|
|
@@ -54,6 +100,10 @@ pub(super) fn transport_has_session(transport: &dyn Transport, session_name: &Se
|
|
|
54
100
|
}
|
|
55
101
|
}
|
|
56
102
|
|
|
103
|
+
/// ---
|
|
104
|
+
/// purpose: 取 spec 请求的显示后端并交给 display 解析
|
|
105
|
+
/// returns: 解析后的后端,spec 未写或写了未知值时为默认 adaptive
|
|
106
|
+
/// ---
|
|
57
107
|
pub(super) fn spec_display_backend(spec: &Value) -> DisplayBackend {
|
|
58
108
|
let requested = spec
|
|
59
109
|
.get("runtime")
|
|
@@ -67,6 +117,12 @@ pub(super) fn spec_display_backend(spec: &Value) -> DisplayBackend {
|
|
|
67
117
|
|
|
68
118
|
use crate::provider::wire::parse_provider;
|
|
69
119
|
|
|
120
|
+
/// ---
|
|
121
|
+
/// purpose: 把 auth_mode 字符串解析成枚举
|
|
122
|
+
/// params:
|
|
123
|
+
/// raw: 只接受 subscription、official_api、compatible_api
|
|
124
|
+
/// returns: 对应枚举,未知取值返回 None
|
|
125
|
+
/// ---
|
|
70
126
|
pub(super) fn parse_auth_mode(raw: &str) -> Option<AuthMode> {
|
|
71
127
|
match raw {
|
|
72
128
|
"subscription" => Some(AuthMode::Subscription),
|
|
@@ -76,6 +132,12 @@ pub(super) fn parse_auth_mode(raw: &str) -> Option<AuthMode> {
|
|
|
76
132
|
}
|
|
77
133
|
}
|
|
78
134
|
|
|
135
|
+
/// ---
|
|
136
|
+
/// purpose: 从原始字符串解析 effort,并要求当前 provider 支持它
|
|
137
|
+
/// params:
|
|
138
|
+
/// raw: effort 原文,None 或空白返回 None
|
|
139
|
+
/// returns: 解析成功且该 provider 支持时返回该档位,否则 None
|
|
140
|
+
/// ---
|
|
79
141
|
/// 0.4.x provider effort MVP step 4: low-level from a raw string. Returns
|
|
80
142
|
/// `Some(effort)` when the level parses AND the provider supports it.
|
|
81
143
|
pub(crate) fn provider_effort_from_raw(
|
|
@@ -94,6 +156,10 @@ pub(crate) fn provider_effort_from_raw(
|
|
|
94
156
|
}
|
|
95
157
|
}
|
|
96
158
|
|
|
159
|
+
/// ---
|
|
160
|
+
/// purpose: 当 spec 请求了该 provider 不支持的 effort 时,给出告警事件载荷
|
|
161
|
+
/// returns: 含 agent_id、provider、effort 与 ignored 动作的 JSON;未请求或本就支持时为 None
|
|
162
|
+
/// ---
|
|
97
163
|
/// 0.4.x provider effort MVP step 7: warning event payload when the spec
|
|
98
164
|
/// requested an effort level the provider does not support.
|
|
99
165
|
pub(crate) fn provider_effort_event_payload(
|
|
@@ -118,6 +184,12 @@ pub(crate) fn provider_effort_event_payload(
|
|
|
118
184
|
}))
|
|
119
185
|
}
|
|
120
186
|
|
|
187
|
+
/// ---
|
|
188
|
+
/// purpose: 对 claude 系 provider 兜底确保 CLAUDE_EFFORT 进入待清除环境变量表
|
|
189
|
+
/// params:
|
|
190
|
+
/// base: 已有的待清除变量名列表
|
|
191
|
+
/// returns: 非 claude 系原样返回;claude 系在缺失时补上 CLAUDE_EFFORT
|
|
192
|
+
/// ---
|
|
121
193
|
/// 0.4.x provider effort MVP step 9: defensive guarantee that `CLAUDE_EFFORT`
|
|
122
194
|
/// is unset in the Claude/ClaudeCode worker spawn env. As of the
|
|
123
195
|
/// `profile_launch::provider_env_unsets` update, the base list already
|
|
@@ -141,6 +213,11 @@ pub(crate) fn extend_worker_env_unset_for_effort(
|
|
|
141
213
|
out
|
|
142
214
|
}
|
|
143
215
|
|
|
216
|
+
/// ---
|
|
217
|
+
/// purpose: 从 YAML agent 节点解析出可用的 effort
|
|
218
|
+
/// returns: 该 provider 支持的 effort,否则 None
|
|
219
|
+
/// contract_id: lifecycle.identity.effort_for_spawn
|
|
220
|
+
/// ---
|
|
144
221
|
/// Convenience: resolve effort for a yaml::Value agent (spec / compiled).
|
|
145
222
|
pub(crate) fn provider_effort_for_spawn(
|
|
146
223
|
agent: &crate::model::yaml::Value,
|
|
@@ -149,6 +226,11 @@ pub(crate) fn provider_effort_for_spawn(
|
|
|
149
226
|
provider_effort_from_raw(agent.get("effort").and_then(|v| v.as_str()), provider)
|
|
150
227
|
}
|
|
151
228
|
|
|
229
|
+
/// ---
|
|
230
|
+
/// purpose: 从 YAML agent 节点取 effort,若被丢弃则给出告警事件载荷
|
|
231
|
+
/// returns: 告警载荷或 None
|
|
232
|
+
/// contract_id: lifecycle.identity.effort_event_if_dropped
|
|
233
|
+
/// ---
|
|
152
234
|
pub(crate) fn provider_effort_event_if_dropped(
|
|
153
235
|
agent: &crate::model::yaml::Value,
|
|
154
236
|
provider: Provider,
|
|
@@ -161,6 +243,11 @@ pub(crate) fn provider_effort_event_if_dropped(
|
|
|
161
243
|
)
|
|
162
244
|
}
|
|
163
245
|
|
|
246
|
+
/// ---
|
|
247
|
+
/// purpose: 从 runtime state 的 JSON agent 节点解析出可用的 effort
|
|
248
|
+
/// returns: 该 provider 支持的 effort,否则 None
|
|
249
|
+
/// contract_id: lifecycle.identity.effort_for_spawn
|
|
250
|
+
/// ---
|
|
164
251
|
/// Same as [`provider_effort_for_spawn`] but for serde_json state values
|
|
165
252
|
/// (used by restart paths reading from `state.agents[id]`).
|
|
166
253
|
pub(crate) fn provider_effort_for_spawn_json(
|
|
@@ -173,6 +260,11 @@ pub(crate) fn provider_effort_for_spawn_json(
|
|
|
173
260
|
)
|
|
174
261
|
}
|
|
175
262
|
|
|
263
|
+
/// ---
|
|
264
|
+
/// purpose: 从 JSON agent 节点取 effort,若被丢弃则给出告警事件载荷
|
|
265
|
+
/// returns: 告警载荷或 None
|
|
266
|
+
/// contract_id: lifecycle.identity.effort_event_if_dropped
|
|
267
|
+
/// ---
|
|
176
268
|
pub(crate) fn provider_effort_event_if_dropped_json(
|
|
177
269
|
agent: &serde_json::Value,
|
|
178
270
|
provider: Provider,
|
|
@@ -185,6 +277,10 @@ pub(crate) fn provider_effort_event_if_dropped_json(
|
|
|
185
277
|
)
|
|
186
278
|
}
|
|
187
279
|
|
|
280
|
+
/// ---
|
|
281
|
+
/// purpose: 定出 quick-start 本次请求的团队键
|
|
282
|
+
/// returns: team_id 优先于 name 的第一个非空值
|
|
283
|
+
/// ---
|
|
188
284
|
pub(super) fn quick_start_requested_team_key<'a>(
|
|
189
285
|
team_id: Option<&'a str>,
|
|
190
286
|
name: Option<&'a str>,
|
|
@@ -197,6 +293,15 @@ pub(super) struct QuickStartDepth {
|
|
|
197
293
|
pub(super) team_depth: u64,
|
|
198
294
|
}
|
|
199
295
|
|
|
296
|
+
/// ---
|
|
297
|
+
/// purpose: 定出本次 quick-start 的父团队键与团队层级
|
|
298
|
+
/// params:
|
|
299
|
+
/// requested_team: 请求的团队键,用于识别 child 或 grandchild 这类嵌套意图
|
|
300
|
+
/// _agents_dir: 未参与判定
|
|
301
|
+
/// _strict_real_runtime: 未参与判定
|
|
302
|
+
/// returns: 父团队键与层级;无父上下文时父键为 None、层级为 1
|
|
303
|
+
/// errors: 已有活跃团队且请求键像嵌套意图但推不出父团队时返回 RequirementUnmet
|
|
304
|
+
/// ---
|
|
200
305
|
pub(super) fn quick_start_depth_guard(
|
|
201
306
|
workspace: &Path,
|
|
202
307
|
_agents_dir: &Path,
|
|
@@ -257,6 +362,10 @@ pub(super) fn quick_start_depth_guard(
|
|
|
257
362
|
})
|
|
258
363
|
}
|
|
259
364
|
|
|
365
|
+
/// ---
|
|
366
|
+
/// purpose: 从 runtime state 推断父团队键
|
|
367
|
+
/// returns: 活跃团队键,且该团队确有 running 席位时返回它,否则 None
|
|
368
|
+
/// ---
|
|
260
369
|
pub(super) fn infer_parent_team_from_active_state(state: &serde_json::Value) -> Option<String> {
|
|
261
370
|
let active = explicit_active_team_key(state)?;
|
|
262
371
|
let team = state
|
|
@@ -266,6 +375,10 @@ pub(super) fn infer_parent_team_from_active_state(state: &serde_json::Value) ->
|
|
|
266
375
|
team_has_running_agent(team).then_some(active)
|
|
267
376
|
}
|
|
268
377
|
|
|
378
|
+
/// ---
|
|
379
|
+
/// purpose: 判断 state 里是否存在带 running 席位的团队
|
|
380
|
+
/// returns: 任一团队有 running 席位即 true
|
|
381
|
+
/// ---
|
|
269
382
|
pub(super) fn has_live_runtime_teams(state: &serde_json::Value) -> bool {
|
|
270
383
|
state
|
|
271
384
|
.get("teams")
|
|
@@ -273,6 +386,10 @@ pub(super) fn has_live_runtime_teams(state: &serde_json::Value) -> bool {
|
|
|
273
386
|
.is_some_and(|teams| teams.values().any(team_has_running_agent))
|
|
274
387
|
}
|
|
275
388
|
|
|
389
|
+
/// ---
|
|
390
|
+
/// purpose: 判断某团队节点里是否有状态为 running 的席位
|
|
391
|
+
/// returns: 有则 true
|
|
392
|
+
/// ---
|
|
276
393
|
pub(super) fn team_has_running_agent(team: &serde_json::Value) -> bool {
|
|
277
394
|
team.get("agents")
|
|
278
395
|
.and_then(serde_json::Value::as_object)
|
|
@@ -283,6 +400,12 @@ pub(super) fn team_has_running_agent(team: &serde_json::Value) -> bool {
|
|
|
283
400
|
})
|
|
284
401
|
}
|
|
285
402
|
|
|
403
|
+
/// ---
|
|
404
|
+
/// purpose: 判断团队键是否像有歧义的子团队名
|
|
405
|
+
/// params:
|
|
406
|
+
/// team: 团队键,比较前去空白并转小写
|
|
407
|
+
/// returns: 以 child 开头但不等于 child 时为 true
|
|
408
|
+
/// ---
|
|
286
409
|
pub(super) fn looks_ambiguous_child_team_key(team: &str) -> bool {
|
|
287
410
|
let team = team.trim().to_ascii_lowercase();
|
|
288
411
|
team != "child"
|
|
@@ -292,6 +415,10 @@ pub(super) fn looks_ambiguous_child_team_key(team: &str) -> bool {
|
|
|
292
415
|
|| team.starts_with("child"))
|
|
293
416
|
}
|
|
294
417
|
|
|
418
|
+
/// ---
|
|
419
|
+
/// purpose: 判断团队键是否像孙团队名
|
|
420
|
+
/// returns: 等于 grandchild 或以 grandchild 开头时为 true
|
|
421
|
+
/// ---
|
|
295
422
|
pub(super) fn looks_grandchild_team_key(team: &str) -> bool {
|
|
296
423
|
let team = team.trim().to_ascii_lowercase();
|
|
297
424
|
team == "grandchild"
|
|
@@ -301,6 +428,12 @@ pub(super) fn looks_grandchild_team_key(team: &str) -> bool {
|
|
|
301
428
|
|| team.starts_with("grandchild")
|
|
302
429
|
}
|
|
303
430
|
|
|
431
|
+
/// ---
|
|
432
|
+
/// purpose: 就地给团队节点写上 team_depth 与可选的 parent_team_key
|
|
433
|
+
/// params:
|
|
434
|
+
/// state: 待改写的团队节点,非对象时不动
|
|
435
|
+
/// parent_team_key: 非空时才写入
|
|
436
|
+
/// ---
|
|
304
437
|
pub(super) fn annotate_team_depth(
|
|
305
438
|
state: &mut serde_json::Value,
|
|
306
439
|
parent_team_key: Option<&str>,
|
|
@@ -315,6 +448,11 @@ pub(super) fn annotate_team_depth(
|
|
|
315
448
|
}
|
|
316
449
|
}
|
|
317
450
|
|
|
451
|
+
/// ---
|
|
452
|
+
/// purpose: 读出 runtime state,给指定团队写上层级与父键后存回
|
|
453
|
+
/// returns: 成功返回空值;state 里没有该团队时直接返回成功且不写盘
|
|
454
|
+
/// errors: 读或写 runtime state 失败时返回 StatePersist
|
|
455
|
+
/// ---
|
|
318
456
|
pub(super) fn annotate_persisted_team_depth(
|
|
319
457
|
workspace: &Path,
|
|
320
458
|
team_key: &str,
|
|
@@ -340,6 +478,10 @@ pub(super) fn annotate_persisted_team_depth(
|
|
|
340
478
|
Ok(())
|
|
341
479
|
}
|
|
342
480
|
|
|
481
|
+
/// ---
|
|
482
|
+
/// purpose: 判断 runtime state 里是否已存在该 quick-start 团队
|
|
483
|
+
/// returns: 活跃键、teams 表、投影出的团队键、身份字段或 session 名任一命中即 true
|
|
484
|
+
/// ---
|
|
343
485
|
pub(super) fn runtime_state_has_quick_start_team(state: &serde_json::Value, team: &str) -> bool {
|
|
344
486
|
explicit_active_team_key(state).as_deref() == Some(team)
|
|
345
487
|
|| state
|
|
@@ -359,6 +501,10 @@ pub(super) fn runtime_state_has_quick_start_team(state: &serde_json::Value, team
|
|
|
359
501
|
.is_some_and(|session| session == team || session.strip_prefix("team-") == Some(team))
|
|
360
502
|
}
|
|
361
503
|
|
|
504
|
+
/// ---
|
|
505
|
+
/// purpose: 判断某 JSON 节点的团队身份字段是否等于给定团队键
|
|
506
|
+
/// returns: team.id、team.name 或顶层 name 命中即 true
|
|
507
|
+
/// ---
|
|
362
508
|
pub(super) fn json_team_identity_matches(state: &serde_json::Value, team: &str) -> bool {
|
|
363
509
|
state
|
|
364
510
|
.get("team")
|