@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
|
@@ -390,6 +390,8 @@ fn compile_role_agent_with_mode(
|
|
|
390
390
|
let id = required_string(&meta, role_path, "name")?;
|
|
391
391
|
let role = required_string(&meta, role_path, "role")?;
|
|
392
392
|
let provider = required_string(&meta, role_path, "provider")?;
|
|
393
|
+
require_explicit_grok_role_model(&meta, role_path, &provider)?;
|
|
394
|
+
require_explicit_cursor_role_model(&meta, role_path, &provider)?;
|
|
393
395
|
let model = resolve_model(&meta, team_meta, &provider);
|
|
394
396
|
let auth_mode = string_field(&meta, "auth_mode")
|
|
395
397
|
.or_else(|| string_field(team_meta, "default_auth_mode"))
|
|
@@ -483,6 +485,15 @@ fn compile_role_agent_with_mode(
|
|
|
483
485
|
effort.as_str()
|
|
484
486
|
)));
|
|
485
487
|
}
|
|
488
|
+
if provider_enum == Provider::CursorAgent {
|
|
489
|
+
return Err(ModelError::Validation(format!(
|
|
490
|
+
"{}: cursor_agent does not support effort '{}'. \
|
|
491
|
+
The Cursor CLI has no `--effort` flag; the framework must not drop the field and still launch. \
|
|
492
|
+
Remove effort from the role file (do not map it into `--model[effort=]` until that form is measured).",
|
|
493
|
+
role_path.display(),
|
|
494
|
+
effort.as_str()
|
|
495
|
+
)));
|
|
496
|
+
}
|
|
486
497
|
agent_items.push(("effort", Value::Str(effort.as_str().to_string())));
|
|
487
498
|
}
|
|
488
499
|
Ok(CompiledRole {
|
|
@@ -550,6 +561,54 @@ fn required_string(meta: &Value, path: &Path, key: &str) -> Result<String, Model
|
|
|
550
561
|
})
|
|
551
562
|
}
|
|
552
563
|
|
|
564
|
+
/// 缺 model 时框架会填内建默认(grok 上是 grok-4),席位因此静默拿到
|
|
565
|
+
/// 与角色文件无关的模型和上下文窗口,而 argv 看起来完全正常。
|
|
566
|
+
/// 角色文件必须自己写死;内建默认、team 级默认、CLI 全局默认都是隐式来源。
|
|
567
|
+
fn require_explicit_grok_role_model(
|
|
568
|
+
meta: &Value,
|
|
569
|
+
path: &Path,
|
|
570
|
+
provider: &str,
|
|
571
|
+
) -> Result<(), ModelError> {
|
|
572
|
+
if parse_canonical_provider(provider) != Some(Provider::Grok) {
|
|
573
|
+
return Ok(());
|
|
574
|
+
}
|
|
575
|
+
if string_field(meta, "model").is_some_and(|value| !value.trim().is_empty()) {
|
|
576
|
+
return Ok(());
|
|
577
|
+
}
|
|
578
|
+
Err(ModelError::Validation(format!(
|
|
579
|
+
"{}: missing front matter field model. \
|
|
580
|
+
Without an explicit model the framework fills a built-in default (grok-4 for grok), \
|
|
581
|
+
so the seat silently gets a model and context window that are not in the role file, \
|
|
582
|
+
while argv still looks normal. The role file must name the model itself; \
|
|
583
|
+
built-in defaults, team-level defaults, and the Grok CLI global default are all implicit sources. Example:\n\
|
|
584
|
+
model: grok-4.6",
|
|
585
|
+
path.display()
|
|
586
|
+
)))
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/// 缺 model 时框架曾填 builtin `sonnet-4-thinking`,本机 shim 还会剥掉该默认。
|
|
590
|
+
/// 角色必须自己写死;内建默认、team 级默认、CLI 全局默认都是隐式来源。
|
|
591
|
+
fn require_explicit_cursor_role_model(
|
|
592
|
+
meta: &Value,
|
|
593
|
+
path: &Path,
|
|
594
|
+
provider: &str,
|
|
595
|
+
) -> Result<(), ModelError> {
|
|
596
|
+
if parse_canonical_provider(provider) != Some(Provider::CursorAgent) {
|
|
597
|
+
return Ok(());
|
|
598
|
+
}
|
|
599
|
+
if string_field(meta, "model").is_some_and(|value| !value.trim().is_empty()) {
|
|
600
|
+
return Ok(());
|
|
601
|
+
}
|
|
602
|
+
Err(ModelError::Validation(format!(
|
|
603
|
+
"{}: missing front matter field model. \
|
|
604
|
+
Without an explicit model the framework fills a built-in default (sonnet-4-thinking for cursor_agent), \
|
|
605
|
+
so the seat silently gets a model that is not in the role file, while argv still looks normal. \
|
|
606
|
+
The role file must name the model itself; built-in defaults, team-level defaults, and the Cursor CLI default are all implicit sources. Example:\n\
|
|
607
|
+
model: sonnet-4-thinking",
|
|
608
|
+
path.display()
|
|
609
|
+
)))
|
|
610
|
+
}
|
|
611
|
+
|
|
553
612
|
/// 0.5.66 bypass 单源:角色 md 的 `dangerously_skip_permissions` 必填 bool。
|
|
554
613
|
/// 缺 = 编译失败(消息含硬串);非 bool = 同 fail-loud。
|
|
555
614
|
fn required_dangerously_skip_permissions(
|
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: coordinator daemon 进程的宿主——装配 transport/registry,跑主循环,把 tick 的崩溃收进退避与事件
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: run_daemon
|
|
6
|
+
//! what: `team-agent coordinator --workspace ..` 子进程入口:解析 transport、构造 Coordinator、进主循环
|
|
7
|
+
//! - name: backoff_sleep_sec
|
|
8
|
+
//! what: 连续失败次数到睡眠秒数的纯函数(5→10→20→40→60→60,封顶 BACKOFF_MAX_SEC)
|
|
9
|
+
//! - name: resolve_tick_interval
|
|
10
|
+
//! what: 解析 tick 间隔并顺带确保 message store schema 可打开
|
|
11
|
+
//! depends:
|
|
12
|
+
//! - super::health
|
|
13
|
+
//! - super::tick
|
|
14
|
+
//! - super::types
|
|
15
|
+
//! - super::conpty_shim
|
|
16
|
+
//! - crate::state::persist
|
|
17
|
+
//! - crate::transport_factory
|
|
18
|
+
//! - crate::tmux_backend
|
|
19
|
+
//! - crate::message_store
|
|
20
|
+
//! - crate::event_log
|
|
21
|
+
//! boundary:
|
|
22
|
+
//! - 不做单次 tick 的编排内容,tick 体在 tick.rs
|
|
23
|
+
//! - 不决定健康与否,只消费 health.rs 的判定与路径
|
|
24
|
+
//! - transport 解析失败时降级为 tmux workspace 后端保活,但不静默——降级原因写进 boot metadata
|
|
25
|
+
//! maturity: wired
|
|
26
|
+
//! ---
|
|
1
27
|
//!
|
|
2
28
|
//! daemon 主循环面(`__main__.py`)—— 退避序列 + tick 间隔解析 + 子进程入口。
|
|
3
29
|
|
|
@@ -41,6 +67,13 @@ pub struct DaemonArgs {
|
|
|
41
67
|
/// daemon 主循环(`main`,`__main__.py:25-98`)。写 pid/meta(source=boot)、装信号→STOP、孤儿自检、
|
|
42
68
|
/// catch-all + 指数退避 + tick_error 去重/抑制、tick_recovered 重置、`result.stop || once` → break。
|
|
43
69
|
/// §10:返 `Result`(顶层 bin 用 anyhow 收;§12 边界)。
|
|
70
|
+
/// ---
|
|
71
|
+
/// purpose: daemon 进程入口——解析 transport 后端、构造 Coordinator、跑主循环直到 stop 或 --once
|
|
72
|
+
/// params:
|
|
73
|
+
/// args: workspace(已 resolve)、once、tick_interval_sec、可选 team_key;team_key 为空时回落到 state.active_team_key
|
|
74
|
+
/// returns: 主循环正常退出(收到 STOP、tick 报 stop、或 --once 跑完一轮)时为 Ok
|
|
75
|
+
/// errors: 写 pid/metadata、事件日志或 tick 间隔解析失败时返回 DaemonError
|
|
76
|
+
/// ---
|
|
44
77
|
pub fn run_daemon(args: DaemonArgs) -> Result<(), DaemonError> {
|
|
45
78
|
// CP-1: the daemon's whole tick surface (has_session / capture / inject / list_windows / kill)
|
|
46
79
|
// runs through this backend. Prefer the persisted runtime endpoint so attached explicit-socket
|
|
@@ -123,6 +156,14 @@ pub fn run_daemon(args: DaemonArgs) -> Result<(), DaemonError> {
|
|
|
123
156
|
run_daemon_with_coordinator_and_boot_tmux(&args, &coordinator, Some(tmux_metadata))
|
|
124
157
|
}
|
|
125
158
|
|
|
159
|
+
/// ---
|
|
160
|
+
/// purpose: 用调用方已装配好的 Coordinator 跑主循环(测试与内部复用入口,跳过 transport 解析)
|
|
161
|
+
/// params:
|
|
162
|
+
/// args: 与 run_daemon 同一份参数
|
|
163
|
+
/// coordinator: 已注入 provider registry 与 transport 的实例
|
|
164
|
+
/// returns: 与 run_daemon 相同的主循环退出语义
|
|
165
|
+
/// errors: 同 run_daemon
|
|
166
|
+
/// ---
|
|
126
167
|
pub(crate) fn run_daemon_with_coordinator(
|
|
127
168
|
args: &DaemonArgs,
|
|
128
169
|
coordinator: &Coordinator,
|
|
@@ -520,6 +561,13 @@ fn current_ppid() -> u32 {
|
|
|
520
561
|
|
|
521
562
|
/// 计算 tick 间隔(`_tick_interval`,`__main__.py:104-115`)。读 spec `runtime.tick_interval_sec`,
|
|
522
563
|
/// 缺失/出错 → `DEFAULT_TICK_INTERVAL_SEC`;并确保 schema 存在(`MessageStore(workspace)`)。
|
|
564
|
+
/// ---
|
|
565
|
+
/// purpose: 取本 workspace 的 tick 间隔秒数,并顺带确保 message store schema 可打开
|
|
566
|
+
/// params:
|
|
567
|
+
/// workspace: 已 resolve 的 workspace 根
|
|
568
|
+
/// returns: tick 间隔秒数;当前实现恒为 DEFAULT_TICK_INTERVAL_SEC(spec 覆盖尚未接线)
|
|
569
|
+
/// errors: MessageStore::open 失败时返回 TickError
|
|
570
|
+
/// ---
|
|
523
571
|
pub(super) fn resolve_tick_interval(workspace: &WorkspacePath) -> Result<f64, TickError> {
|
|
524
572
|
let _ = MessageStore::open(workspace.as_path())?;
|
|
525
573
|
Ok(DEFAULT_TICK_INTERVAL_SEC)
|
|
@@ -527,6 +575,13 @@ pub(super) fn resolve_tick_interval(workspace: &WorkspacePath) -> Result<f64, Ti
|
|
|
527
575
|
|
|
528
576
|
/// 退避序列(`__main__.py:65`):`min(interval * 2^min(failures-1, 5), 60.0)` → 5→10→20→40→60→60s。
|
|
529
577
|
/// unit test 锁死本序列(card §85)。**纯函数,无 I/O,可直接 impl 钉死**(但 ROUND-0 仍占位)。
|
|
578
|
+
/// ---
|
|
579
|
+
/// purpose: 由连续失败次数算出本轮该睡多少秒的纯函数
|
|
580
|
+
/// params:
|
|
581
|
+
/// interval: 基准 tick 间隔秒数
|
|
582
|
+
/// consecutive_failures: 至今连续失败次数;0 与 1 同视为首次失败
|
|
583
|
+
/// returns: interval * 2^min(failures-1, 5),上限 BACKOFF_MAX_SEC
|
|
584
|
+
/// ---
|
|
530
585
|
pub(super) fn backoff_sleep_sec(interval: f64, consecutive_failures: u32) -> f64 {
|
|
531
586
|
let failures = consecutive_failures.saturating_sub(1).min(5);
|
|
532
587
|
let exp = i32::try_from(failures).unwrap_or(5);
|
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: Windows ConPTY shim 的生命周期主人——定位并启动 windows-shim.exe、做 Hello 握手、把可复用的连接与 pid/pipe 名记进 state
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: ensure_shim_running
|
|
6
|
+
//! what: 幂等入口:state 里记的 shim 还活着就重连,否则新起一个并握手
|
|
7
|
+
//! - name: reconnect_recorded_shim
|
|
8
|
+
//! what: 只按 state 记的 pipe 名重连并重做 Hello,绝不新起进程
|
|
9
|
+
//! - name: recorded_shim_pid
|
|
10
|
+
//! what: 读 state.transport.shim.pid,供停机路由
|
|
11
|
+
//! - name: recorded_shim_pipe_name
|
|
12
|
+
//! what: 读 state.transport.shim.pipe_name,供重连路由
|
|
13
|
+
//! - name: mark_transport_unavailable
|
|
14
|
+
//! what: shim 不可达时发 stale 家族事件并清掉 state 里的 pipe_ready
|
|
15
|
+
//! depends:
|
|
16
|
+
//! - conpty_transport
|
|
17
|
+
//! - crate::state::repository
|
|
18
|
+
//! - crate::state::persist
|
|
19
|
+
//! - crate::event_log
|
|
20
|
+
//! - crate::platform::process
|
|
21
|
+
//! boundary:
|
|
22
|
+
//! - pipe_token 绝不落 state:只经子进程环境变量传递并留在本进程内存里,state 只记 pid/pipe_name/pipe_ready
|
|
23
|
+
//! - 终态失败不静默回落 tmux,交由调用方按传输不可用处理
|
|
24
|
+
//! - 重连只证明线协议可用,不做新的令牌认证
|
|
25
|
+
//! - 整个文件仅在 Windows 编译;非 Windows 调用方必须自己 cfg 门控
|
|
26
|
+
//! maturity: wired
|
|
27
|
+
//! ---
|
|
1
28
|
//!
|
|
2
29
|
//! Windows ConPTY shim lifecycle manager.
|
|
3
30
|
//!
|
|
@@ -75,16 +102,28 @@ pub struct ShimHandle {
|
|
|
75
102
|
impl ShimHandle {
|
|
76
103
|
/// The connected pipe client, ready for the factory to wire into
|
|
77
104
|
/// `ConPtyBackend`. Callable exactly once — takes ownership.
|
|
105
|
+
/// ---
|
|
106
|
+
/// purpose: 取走已完成 Hello 握手的管道客户端,交给传输后端接线
|
|
107
|
+
/// returns: 首次调用返回客户端并转移所有权;再次调用为 None
|
|
108
|
+
/// ---
|
|
78
109
|
pub fn take_client(&mut self) -> Option<NamedPipeClient> {
|
|
79
110
|
self.client.take()
|
|
80
111
|
}
|
|
81
112
|
|
|
82
113
|
/// The shim pid (for status + shutdown routing).
|
|
114
|
+
/// ---
|
|
115
|
+
/// purpose: 给出 shim 进程号,用于状态展示与停机路由
|
|
116
|
+
/// returns: 启动时记下的 pid;子进程句柄已被 detach 或 drop 后该值仍可读
|
|
117
|
+
/// ---
|
|
83
118
|
pub fn pid(&self) -> u32 {
|
|
84
119
|
self.pid
|
|
85
120
|
}
|
|
86
121
|
|
|
87
122
|
/// The pipe name the shim listens on.
|
|
123
|
+
/// ---
|
|
124
|
+
/// purpose: 给出 shim 正在监听的命名管道名
|
|
125
|
+
/// returns: 形如 team-agent-conpty-<hash>-<team> 的管道全名
|
|
126
|
+
/// ---
|
|
88
127
|
pub fn pipe_name(&self) -> &str {
|
|
89
128
|
&self.pipe_name
|
|
90
129
|
}
|
|
@@ -95,6 +134,11 @@ impl ShimHandle {
|
|
|
95
134
|
/// an explicit `platform::process::terminate_pid` via
|
|
96
135
|
/// `recorded_shim_pid`. Without this call, going out of scope
|
|
97
136
|
/// would terminate the shim and orphan every worker.
|
|
137
|
+
/// ---
|
|
138
|
+
/// purpose: 放弃对 shim 子进程的所有权,使本句柄析构时不再杀它
|
|
139
|
+
/// returns: shim 的 pid,供调用方日后按精确 pid 显式终止
|
|
140
|
+
/// ---
|
|
141
|
+
/// 不调用它就让句柄离开作用域,会连带杀掉 shim 并使全部 worker 变成孤儿。
|
|
98
142
|
pub fn detach(mut self) -> u32 {
|
|
99
143
|
// Forget the child so its own Drop doesn't run either. The
|
|
100
144
|
// shim survives until an explicit kill.
|
|
@@ -453,6 +497,12 @@ fn finalize(
|
|
|
453
497
|
/// `None` when no shim is currently registered (Unix / never-launched
|
|
454
498
|
/// Windows worker). Callers use `platform::process::terminate_pid`
|
|
455
499
|
/// on the returned pid.
|
|
500
|
+
/// ---
|
|
501
|
+
/// purpose: 从 state 读出已登记的 shim 进程号
|
|
502
|
+
/// params:
|
|
503
|
+
/// workspace: workspace 根
|
|
504
|
+
/// returns: state.transport.shim.pid 存在且能转成 u32 时给值;没有登记过 shim(未起过或非 Windows 队)时为 None。只读状态,不做存活探测
|
|
505
|
+
/// ---
|
|
456
506
|
pub fn recorded_shim_pid(workspace: &Path) -> Option<u32> {
|
|
457
507
|
let state = crate::state::repository::StateRepository::new(workspace)
|
|
458
508
|
.load_workspace_if_exists_without_migrations()
|
|
@@ -466,6 +516,12 @@ pub fn recorded_shim_pid(workspace: &Path) -> Option<u32> {
|
|
|
466
516
|
}
|
|
467
517
|
|
|
468
518
|
/// Read `state.transport.shim.pipe_name` for reconnect routing.
|
|
519
|
+
/// ---
|
|
520
|
+
/// purpose: 从 state 读出已登记的 shim 管道名
|
|
521
|
+
/// params:
|
|
522
|
+
/// workspace: workspace 根
|
|
523
|
+
/// returns: state.transport.shim.pipe_name 的字符串值;未登记时为 None
|
|
524
|
+
/// ---
|
|
469
525
|
pub fn recorded_shim_pipe_name(workspace: &Path) -> Option<String> {
|
|
470
526
|
let state = crate::state::repository::StateRepository::new(workspace)
|
|
471
527
|
.load_workspace_if_exists_without_migrations()
|
|
@@ -497,6 +553,15 @@ pub fn recorded_shim_pipe_name(workspace: &Path) -> Option<String> {
|
|
|
497
553
|
/// This gives the "coord can die, shim survives" invariant: a fresh
|
|
498
554
|
/// coord that finds a live shim just reconnects; a fresh coord that
|
|
499
555
|
/// finds no shim spawns one.
|
|
556
|
+
/// ---
|
|
557
|
+
/// purpose: 保证本 (workspace, team) 有一个可用的 shim——这是 coordinator 侧唯一的 shim 所有权入口
|
|
558
|
+
/// params:
|
|
559
|
+
/// workspace: workspace 根
|
|
560
|
+
/// team_key: 目标 team,进 Hello 请求的作用域
|
|
561
|
+
/// workspace_hash: 管道名里用的 workspace 摘要
|
|
562
|
+
/// returns: 已登记 pid 还活着且重连成功 → 复用原进程的句柄(不拥有子进程);否则新起一个并握手,返回拥有子进程的句柄
|
|
563
|
+
/// errors: 找不到 shim 可执行文件、spawn 失败、连接超时、Hello 失败或落盘 state 失败时返回对应 ShimError。重连失败不算错误,会直接落到新起那条路
|
|
564
|
+
/// ---
|
|
500
565
|
pub fn ensure_shim_running(
|
|
501
566
|
workspace: &Path,
|
|
502
567
|
team_key: &str,
|
|
@@ -541,6 +606,15 @@ pub fn ensure_shim_running(
|
|
|
541
606
|
/// Handle returned from this fn has no owned child (the shim is
|
|
542
607
|
/// somebody else's — usually a previous coord instance). `detach()`
|
|
543
608
|
/// on the handle is a no-op for the child.
|
|
609
|
+
/// ---
|
|
610
|
+
/// purpose: 只重连 state 里已登记的 shim 并重做 Hello,用来证明线协议仍然可用
|
|
611
|
+
/// params:
|
|
612
|
+
/// workspace: workspace 根
|
|
613
|
+
/// team_key: 目标 team,进 Hello 请求的作用域
|
|
614
|
+
/// _workspace_hash: 未使用;管道名直接取自 state,不再重算
|
|
615
|
+
/// returns: 连上且 Hello 回 ok 时给出不拥有子进程的句柄,对它调 detach 对子进程是空操作
|
|
616
|
+
/// errors: state 里没有管道名或多次连接均失败 → ConnectTimeout;连上但 Hello 回 ok=false → HelloFailed。本函数绝不新起进程
|
|
617
|
+
/// ---
|
|
544
618
|
pub fn reconnect_recorded_shim(
|
|
545
619
|
workspace: &Path,
|
|
546
620
|
team_key: &str,
|
|
@@ -630,6 +704,14 @@ fn reconnect_hello(client: &mut NamedPipeClient, team_key: &str) -> Result<(), S
|
|
|
630
704
|
/// The Windows-only `#[cfg]` gate is at the mod level (see
|
|
631
705
|
/// `coordinator/mod.rs`); on Unix this file isn't compiled, so
|
|
632
706
|
/// downstream callers must cfg-gate their reference themselves.
|
|
707
|
+
/// ---
|
|
708
|
+
/// purpose: 把「shim 不可达」这件事诚实地写出去——发 stale 家族事件,并清掉 state 里的就绪位
|
|
709
|
+
/// params:
|
|
710
|
+
/// workspace: workspace 根
|
|
711
|
+
/// reason: 不可达原因,同时进事件与 state.transport.shim.unavailable_reason
|
|
712
|
+
/// returns: 状态里本来就没有 shim 块时也返回 Ok。事件写入是尽力而为,写不出去不影响返回;清状态才是权威动作
|
|
713
|
+
/// errors: 读写 state 失败时返回 StateError;state JSON 损坏时按空状态继续
|
|
714
|
+
/// ---
|
|
633
715
|
pub fn mark_transport_unavailable(workspace: &Path, reason: &str) -> Result<(), StateError> {
|
|
634
716
|
// Best-effort event emission — a failed event write should not
|
|
635
717
|
// fail the caller. The state-clearing step below is authoritative.
|
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: coordinator daemon 的健康判定、幂等启停与只读观测面——pid/metadata/schema 三合一健康、spawn 与终止、runtime 路径、以及 team-agent watch 的事件渲染
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: coordinator_health
|
|
6
|
+
//! what: 由 pid 文件、coordinator.json 与 message store schema 合成 HealthReport,ok 与 service_available 分开表达
|
|
7
|
+
//! - name: start_coordinator
|
|
8
|
+
//! what: 幂等启动:已健康则 no-op,metadata 不兼容先停再起,schema 不兼容拒启并给修复 hint
|
|
9
|
+
//! - name: start_coordinator_with_team
|
|
10
|
+
//! what: 同上,并把 team_key 以 --team 传给子进程,免得 daemon 自己从 state 推
|
|
11
|
+
//! - name: stop_coordinator
|
|
12
|
+
//! what: 终止 daemon 并清 pid/meta;pid 文件缺失时用 ps 扫描发现流浪 coordinator
|
|
13
|
+
//! - name: collect_watch_lines
|
|
14
|
+
//! what: 从 events.jsonl 与结果表增量取出可渲染行,并推进 WatchCursor
|
|
15
|
+
//! - name: render_event_line
|
|
16
|
+
//! what: 把一条结构化事件渲染成人类可读行,不认识的事件返回 None
|
|
17
|
+
//! - name: run_watch
|
|
18
|
+
//! what: team-agent watch 主循环:反复 collect 后输出并 sleep
|
|
19
|
+
//! - name: coordinator_pid_path
|
|
20
|
+
//! what: coordinator.pid 的位置
|
|
21
|
+
//! - name: coordinator_meta_path
|
|
22
|
+
//! what: coordinator.json 的位置
|
|
23
|
+
//! - name: coordinator_log_path
|
|
24
|
+
//! what: coordinator.log 的位置
|
|
25
|
+
//! depends:
|
|
26
|
+
//! - super::types
|
|
27
|
+
//! - crate::message_store
|
|
28
|
+
//! - crate::db::schema
|
|
29
|
+
//! - crate::event_log
|
|
30
|
+
//! - crate::model::paths
|
|
31
|
+
//! - crate::packaging
|
|
32
|
+
//! - crate::platform::process
|
|
33
|
+
//! - crate::os_probe
|
|
34
|
+
//! boundary:
|
|
35
|
+
//! - 不做 tick 编排,也不投递任何消息
|
|
36
|
+
//! - 不读 provider 凭据、不碰 .env;身份只取自当前可执行文件与已落盘 metadata
|
|
37
|
+
//! - 终止进程限定本 workspace:优先按本次判定拿到的精确 pid;pid 文件缺失时的流浪回收会按 ps 命令行匹配「coordinator --workspace <本 ws>」发现目标,仍不做跨 workspace 的 pkill/killall 泛清
|
|
38
|
+
//! - watch 侧只读:不重放已归档段,rotation 只插一条 marker 并重置 offset
|
|
39
|
+
//! maturity: wired
|
|
40
|
+
//! ---
|
|
1
41
|
//!
|
|
2
42
|
//! coordinator 健康/身份 & 只读可观测面:metadata 身份原语 + coordinator 路径 + watch 实时流。
|
|
3
43
|
|
|
@@ -27,6 +67,12 @@ use super::types::{
|
|
|
27
67
|
// ===========================================================================
|
|
28
68
|
|
|
29
69
|
/// `coordinator_health`(`lifecycle.py:38-46`):`running ∧ metadata_ok ∧ schema_ok` → typed report.
|
|
70
|
+
/// ---
|
|
71
|
+
/// purpose: 一次性判定本 workspace 的 coordinator daemon 是否健康
|
|
72
|
+
/// params:
|
|
73
|
+
/// workspace: workspace 根;pid/metadata 路径与 message store 都由它派生
|
|
74
|
+
/// returns: HealthReport。ok = 进程在跑 ∧ metadata 三元全等 ∧ 二进制身份一致 ∧ schema 兼容;service_available 刻意排除二进制身份,表示「这个 daemon 还能处理本队队列」;status 区分 Missing / InvalidPid / Running / Stale
|
|
75
|
+
/// ---
|
|
30
76
|
pub fn coordinator_health(workspace: &WorkspacePath) -> HealthReport {
|
|
31
77
|
let schema = message_store_schema_health(workspace);
|
|
32
78
|
let current_binary_identity = current_coordinator_binary_identity();
|
|
@@ -73,6 +119,13 @@ pub fn coordinator_health(workspace: &WorkspacePath) -> HealthReport {
|
|
|
73
119
|
|
|
74
120
|
/// `start_coordinator`(`lifecycle.py:49-121`):幂等 — 已健康 no-op(AlreadyRunning);metadata 不兼容
|
|
75
121
|
/// 先 stop 再起;schema 不兼容拒启 + hint;否则 spawn `team-agent coordinator --workspace <ws>`。
|
|
122
|
+
/// ---
|
|
123
|
+
/// purpose: 不带 team_key 的幂等启动入口
|
|
124
|
+
/// params:
|
|
125
|
+
/// workspace: workspace 根
|
|
126
|
+
/// returns: 与 start_coordinator_with_team(workspace, None) 完全一致
|
|
127
|
+
/// errors: 同 start_coordinator_with_team
|
|
128
|
+
/// ---
|
|
76
129
|
pub fn start_coordinator(workspace: &WorkspacePath) -> Result<StartReport, StartError> {
|
|
77
130
|
start_coordinator_with_team(workspace, None)
|
|
78
131
|
}
|
|
@@ -85,6 +138,14 @@ pub fn start_coordinator(workspace: &WorkspacePath) -> Result<StartReport, Start
|
|
|
85
138
|
///
|
|
86
139
|
/// Callers that CAN pass team_key (Batch 9 quick-start Windows path)
|
|
87
140
|
/// SHOULD — that avoids Batch 8's F8 seed-state trap.
|
|
141
|
+
/// ---
|
|
142
|
+
/// purpose: 幂等启动 coordinator daemon 子进程,并把不兼容/需轮换的情形分成互不折叠的结局
|
|
143
|
+
/// params:
|
|
144
|
+
/// workspace: workspace 根
|
|
145
|
+
/// team_key: 传给子进程的 --team;None 或空串时子进程回落到 state.active_team_key
|
|
146
|
+
/// returns: StartReport。已健康 → AlreadyRunning(含「daemon 比调用方新,保留不动」这一支,rotation_reason=daemon_newer_than_caller);schema 不兼容 → SchemaIncompatible 且 ok=false 并带修复 hint;在跑但 wire metadata 不兼容、或 metadata 指向调用方自身、或先停失败 → RestartIncompatibleStopFailed;成功 spawn → Started,因身份轮换而重起则为 StartedAfterRotation
|
|
147
|
+
/// errors: 建目录、开日志、spawn、写 pid/metadata 或写事件失败时返回 StartError;「拒启」不是 Err,而是 ok=false 的报告
|
|
148
|
+
/// ---
|
|
88
149
|
pub fn start_coordinator_with_team(
|
|
89
150
|
workspace: &WorkspacePath,
|
|
90
151
|
team_key: Option<&str>,
|
|
@@ -322,6 +383,13 @@ fn detach_daemon_child(command: &mut Command) {
|
|
|
322
383
|
fn detach_daemon_child(_command: &mut Command) {}
|
|
323
384
|
|
|
324
385
|
/// `stop_coordinator`(`lifecycle.py:228-247`):SIGTERM pid + 清 pid/meta → typed report。
|
|
386
|
+
/// ---
|
|
387
|
+
/// purpose: 停掉本 workspace 的 coordinator daemon 并清掉 pid/meta 文件
|
|
388
|
+
/// params:
|
|
389
|
+
/// workspace: workspace 根
|
|
390
|
+
/// returns: StopReport。pid 文件不存在时先尝试按 ps 发现流浪 coordinator,仍没有则 Missing;pid 文件内容非法 → 清文件并报 InvalidPidRemoved;终止成功 → Stopped;信号发不出去 → KillFailed
|
|
391
|
+
/// errors: 删 pid/meta 文件失败时返回 StopError;本函数不写事件(EventLog 变体在此路径无产生点)
|
|
392
|
+
/// ---
|
|
325
393
|
pub fn stop_coordinator(workspace: &WorkspacePath) -> Result<StopReport, StopError> {
|
|
326
394
|
let pid_path = coordinator_pid_path(workspace);
|
|
327
395
|
if !pid_path.exists() {
|
|
@@ -498,6 +566,12 @@ fn terminate_pid(pid: Pid) -> bool {
|
|
|
498
566
|
|
|
499
567
|
/// Public wrapper for diagnostic cleanup paths that must reuse coordinator
|
|
500
568
|
/// shutdown's SIGTERM-then-SIGKILL semantics.
|
|
569
|
+
/// ---
|
|
570
|
+
/// purpose: 把 coordinator 停机用的「先温和后强制」终止语义暴露给诊断清理路径复用
|
|
571
|
+
/// params:
|
|
572
|
+
/// pid: 要终止的进程;只终止这棵进程树,不做名字匹配的批量清理
|
|
573
|
+
/// returns: 超时窗口内整棵树都不再存活为 true
|
|
574
|
+
/// ---
|
|
501
575
|
pub fn terminate_pid_tree(pid: Pid) -> bool {
|
|
502
576
|
terminate_pid(pid)
|
|
503
577
|
}
|
|
@@ -607,6 +681,14 @@ fn wait_until_not_running(pid: Pid, timeout: Duration) -> bool {
|
|
|
607
681
|
/// `ps stat=` step has no analogue). This preserves the coordinator's
|
|
608
682
|
/// "am I the owner" check without silently reporting stale pids as
|
|
609
683
|
/// alive.
|
|
684
|
+
/// ---
|
|
685
|
+
/// purpose: 判断某 pid 是不是本 coordinator 还能拥有的活进程(Unix 实现)
|
|
686
|
+
/// params:
|
|
687
|
+
/// pid: 待判定进程号
|
|
688
|
+
/// returns: 存活且非僵尸为 true。语义与通用存活探针不同:signal 返回 EPERM 一律判 false,因为 coordinator 只认自己能发信号的进程;另用 ps 的 stat 排掉僵尸
|
|
689
|
+
/// errors: 除 EPERM/ESRCH 外的 signal 错误、以及 ps 探测失败时返回 io::Error
|
|
690
|
+
/// cfg: unix
|
|
691
|
+
/// ---
|
|
610
692
|
#[cfg(unix)]
|
|
611
693
|
pub fn pid_is_running(pid: Pid) -> Result<bool, std::io::Error> {
|
|
612
694
|
let Ok(pid_t) = libc::pid_t::try_from(pid.get()) else {
|
|
@@ -640,6 +722,14 @@ pub fn pid_is_running(pid: Pid) -> Result<bool, std::io::Error> {
|
|
|
640
722
|
/// treats as `Live`; so on Windows the coordinator sees a process
|
|
641
723
|
/// it can't query as still-running (safer than pretending it's gone
|
|
642
724
|
/// and losing the ownership handle).
|
|
725
|
+
/// ---
|
|
726
|
+
/// purpose: 判断某 pid 是不是本 coordinator 还能拥有的活进程(非 Unix 实现)
|
|
727
|
+
/// params:
|
|
728
|
+
/// pid: 待判定进程号
|
|
729
|
+
/// returns: 平台层报 Live 为 true,Dead 或 Unknown 均为 false。Windows 没有僵尸态,故不做 ps stat 那一步;平台层把拒绝访问算作 Live,于是查不动的进程仍被视为在跑
|
|
730
|
+
/// errors: 平台层存活查询失败时返回 io::Error
|
|
731
|
+
/// cfg: not(unix)
|
|
732
|
+
/// ---
|
|
643
733
|
#[cfg(not(unix))]
|
|
644
734
|
pub fn pid_is_running(pid: Pid) -> Result<bool, std::io::Error> {
|
|
645
735
|
match crate::platform::process::pid_liveness(pid.get())? {
|
|
@@ -650,11 +740,21 @@ pub fn pid_is_running(pid: Pid) -> Result<bool, std::io::Error> {
|
|
|
650
740
|
}
|
|
651
741
|
|
|
652
742
|
/// `read_coordinator_metadata`(`metadata.py:28-34`)。读 `coordinator.json`;损坏/缺失/非 dict → `None`。
|
|
743
|
+
/// ---
|
|
744
|
+
/// purpose: 读出已落盘的 coordinator.json
|
|
745
|
+
/// params:
|
|
746
|
+
/// workspace: workspace 根
|
|
747
|
+
/// returns: 解析成功才有值;文件缺失、读不动或 JSON 形状不符一律为 None,绝不返回半份 metadata
|
|
748
|
+
/// ---
|
|
653
749
|
pub fn read_coordinator_metadata(workspace: &WorkspacePath) -> Option<CoordinatorMetadata> {
|
|
654
750
|
let text = std::fs::read_to_string(coordinator_meta_path(workspace)).ok()?;
|
|
655
751
|
serde_json::from_str(&text).ok()
|
|
656
752
|
}
|
|
657
753
|
|
|
754
|
+
/// ---
|
|
755
|
+
/// purpose: 给出「当前这个 CLI 二进制」的身份,用于和 daemon 已记录的身份比对
|
|
756
|
+
/// returns: 路径取自当前可执行文件(尽量 canonicalize)而非 PATH 查找,版本取自编译进来的包版本;路径取不到时退化成 <unknown>。测试可用 TEAM_AGENT_TEST_CALLER_BINARY_IDENTITY 覆盖,且只有两字段都非空才采信
|
|
757
|
+
/// ---
|
|
658
758
|
pub fn current_coordinator_binary_identity() -> CoordinatorBinaryIdentity {
|
|
659
759
|
if let Ok(raw) = std::env::var("TEAM_AGENT_TEST_CALLER_BINARY_IDENTITY") {
|
|
660
760
|
if let Ok(identity) = serde_json::from_str::<CoordinatorBinaryIdentity>(&raw) {
|
|
@@ -675,10 +775,24 @@ pub fn current_coordinator_binary_identity() -> CoordinatorBinaryIdentity {
|
|
|
675
775
|
|
|
676
776
|
/// `coordinator_metadata_ok` now includes daemon binary identity in addition
|
|
677
777
|
/// to the original pid/protocol/schema tuple.
|
|
778
|
+
/// ---
|
|
779
|
+
/// purpose: 判断已落盘 metadata 是否与当前事实完全一致
|
|
780
|
+
/// params:
|
|
781
|
+
/// metadata: 已读出的 coordinator.json;None 视为不一致
|
|
782
|
+
/// pid: 实际观测到的 daemon pid
|
|
783
|
+
/// returns: pid、协议版本、message store schema 版本、以及 daemon 二进制身份四者全对才为 true
|
|
784
|
+
/// ---
|
|
678
785
|
pub fn coordinator_metadata_ok(metadata: Option<&CoordinatorMetadata>, pid: Pid) -> bool {
|
|
679
786
|
coordinator_metadata_mismatch_reason(metadata, pid).is_none()
|
|
680
787
|
}
|
|
681
788
|
|
|
789
|
+
/// ---
|
|
790
|
+
/// purpose: 给出 metadata 不一致的机器可读原因,而不是只给一个布尔
|
|
791
|
+
/// params:
|
|
792
|
+
/// metadata: 已读出的 coordinator.json;None 时原因为 MetadataMissing
|
|
793
|
+
/// pid: 实际观测到的 daemon pid
|
|
794
|
+
/// returns: 第一个不匹配项对应的原因;全部一致时为 None。先判 pid/协议/schema 这组线协议字段,再判二进制身份
|
|
795
|
+
/// ---
|
|
682
796
|
pub fn coordinator_metadata_mismatch_reason(
|
|
683
797
|
metadata: Option<&CoordinatorMetadata>,
|
|
684
798
|
pid: Pid,
|
|
@@ -833,6 +947,15 @@ fn path_matches(metadata_path: &str, path: &Path) -> bool {
|
|
|
833
947
|
|
|
834
948
|
/// `write_coordinator_metadata`(`metadata.py:46-61`)。写 `coordinator.json`(pretty indent=2),
|
|
835
949
|
/// `updated_at = now(utc).isoformat()`。
|
|
950
|
+
/// ---
|
|
951
|
+
/// purpose: 落盘 coordinator.json,把当前 daemon 的身份三元与二进制身份记下来
|
|
952
|
+
/// params:
|
|
953
|
+
/// workspace: workspace 根
|
|
954
|
+
/// pid: 本次要记录的 daemon pid
|
|
955
|
+
/// source: 这份 metadata 是 daemon 自举时写的还是 CLI start 时写的
|
|
956
|
+
/// returns: 写成功返回 ()。协议版本与 schema 版本取自当前构建常量,updated_at 是写入时刻的 UTC
|
|
957
|
+
/// errors: 建目录、序列化或写文件失败时返回 io::Error
|
|
958
|
+
/// ---
|
|
836
959
|
pub fn write_coordinator_metadata(
|
|
837
960
|
workspace: &WorkspacePath,
|
|
838
961
|
pid: Pid,
|
|
@@ -856,6 +979,12 @@ pub fn write_coordinator_metadata(
|
|
|
856
979
|
std::fs::write(path, text)
|
|
857
980
|
}
|
|
858
981
|
|
|
982
|
+
/// ---
|
|
983
|
+
/// purpose: 用「能不能真的打开本队 message store」来判 schema 兼容门
|
|
984
|
+
/// params:
|
|
985
|
+
/// workspace: workspace 根
|
|
986
|
+
/// returns: 打开成功则 ok=true 且 error/action 为空;失败则 ok=false,带 InitFailed 原文与修复 hint。schema_version 恒为当前构建的版本号
|
|
987
|
+
/// ---
|
|
859
988
|
pub(crate) fn message_store_schema_health(workspace: &WorkspacePath) -> SchemaHealth {
|
|
860
989
|
match MessageStore::open(workspace.as_path()) {
|
|
861
990
|
Ok(_) => SchemaHealth {
|
|
@@ -894,16 +1023,34 @@ fn remove_file_if_exists(path: &Path) -> Result<(), std::io::Error> {
|
|
|
894
1023
|
// ===========================================================================
|
|
895
1024
|
|
|
896
1025
|
/// `coordinator.pid` 路径(`paths.py:8`)= `runtime_dir(workspace)/coordinator.pid`。
|
|
1026
|
+
/// ---
|
|
1027
|
+
/// purpose: 给出 coordinator.pid 的位置
|
|
1028
|
+
/// params:
|
|
1029
|
+
/// workspace: workspace 根
|
|
1030
|
+
/// returns: runtime 目录下的 coordinator.pid;只算路径,不保证文件存在
|
|
1031
|
+
/// ---
|
|
897
1032
|
pub fn coordinator_pid_path(workspace: &WorkspacePath) -> PathBuf {
|
|
898
1033
|
crate::model::paths::runtime_dir(workspace.as_path()).join("coordinator.pid")
|
|
899
1034
|
}
|
|
900
1035
|
|
|
901
1036
|
/// `coordinator.json` 路径(`paths.py:12`)。
|
|
1037
|
+
/// ---
|
|
1038
|
+
/// purpose: 给出 coordinator.json 的位置
|
|
1039
|
+
/// params:
|
|
1040
|
+
/// workspace: workspace 根
|
|
1041
|
+
/// returns: runtime 目录下的 coordinator.json;只算路径,不保证文件存在
|
|
1042
|
+
/// ---
|
|
902
1043
|
pub fn coordinator_meta_path(workspace: &WorkspacePath) -> PathBuf {
|
|
903
1044
|
crate::model::paths::runtime_dir(workspace.as_path()).join("coordinator.json")
|
|
904
1045
|
}
|
|
905
1046
|
|
|
906
1047
|
/// `coordinator.log` 路径(`paths.py:16`)。
|
|
1048
|
+
/// ---
|
|
1049
|
+
/// purpose: 给出 coordinator.log 的位置
|
|
1050
|
+
/// params:
|
|
1051
|
+
/// workspace: workspace 根
|
|
1052
|
+
/// returns: runtime 目录下的 coordinator.log;daemon 子进程的 stdout/stderr 都追加到这里
|
|
1053
|
+
/// ---
|
|
907
1054
|
pub fn coordinator_log_path(workspace: &WorkspacePath) -> PathBuf {
|
|
908
1055
|
crate::model::paths::runtime_dir(workspace.as_path()).join("coordinator.log")
|
|
909
1056
|
}
|
|
@@ -915,6 +1062,16 @@ pub fn coordinator_log_path(workspace: &WorkspacePath) -> PathBuf {
|
|
|
915
1062
|
/// `collect_watch_lines`(`watch.py:40`)。tail events.jsonl(过滤 team)+ latest_results,
|
|
916
1063
|
/// 渲染人类可读行;处理 log rotation(ROTATION_MARKER + offset 重置,不重放历史段)。
|
|
917
1064
|
/// 推进 `cursor`。
|
|
1065
|
+
/// ---
|
|
1066
|
+
/// purpose: 增量取出自上次游标以来的可渲染 watch 行(事件 + 结果两路)
|
|
1067
|
+
/// params:
|
|
1068
|
+
/// workspace: workspace 根
|
|
1069
|
+
/// cursor: 可变游标,函数会推进 offset、已见结果 id 集合与归档签名
|
|
1070
|
+
/// store: 已打开的 message store,用于取结果行
|
|
1071
|
+
/// team: 只看这个 team 的事件;None 表示不过滤
|
|
1072
|
+
/// returns: 本次新增的渲染行,事件行在前、结果行在后;无新内容时为空 Vec
|
|
1073
|
+
/// errors: 读事件文件或查库失败时返回 WatchError
|
|
1074
|
+
/// ---
|
|
918
1075
|
pub fn collect_watch_lines(
|
|
919
1076
|
workspace: &WorkspacePath,
|
|
920
1077
|
cursor: &mut WatchCursor,
|
|
@@ -1031,6 +1188,12 @@ fn collect_result_lines(
|
|
|
1031
1188
|
/// `render_event_line`(`watch.py:46-63`)。把一条 step 4 事件渲染成人类可读行;非可渲染事件 → `None`。
|
|
1032
1189
|
/// 消费的事件类型:`result_received` / `leader_receiver.{injected,submitted}` / `send.failed` /
|
|
1033
1190
|
/// `leader_receiver.rebind_required` / `leader.api_error`(card 表)。
|
|
1191
|
+
/// ---
|
|
1192
|
+
/// purpose: 把一条结构化事件渲染成一行人类可读文本
|
|
1193
|
+
/// params:
|
|
1194
|
+
/// event: 事件 JSON 对象;靠其中的 event 字段分派
|
|
1195
|
+
/// returns: 已知事件类型返回渲染行,其余一律 None(不猜、不打印原始 JSON)。摘要字段做长度截断
|
|
1196
|
+
/// ---
|
|
1034
1197
|
pub fn render_event_line(event: &Value) -> Option<String> {
|
|
1035
1198
|
let event_name = event.get("event").and_then(Value::as_str)?;
|
|
1036
1199
|
match event_name {
|
|
@@ -1087,6 +1250,16 @@ pub fn render_event_line(event: &Value) -> Option<String> {
|
|
|
1087
1250
|
|
|
1088
1251
|
/// `run_watch`(`watch.py:25`)。`team-agent watch` 主循环:反复 `collect_watch_lines` + 输出 + sleep。
|
|
1089
1252
|
/// `output`/`sleep` 注入便于测试。§10 返 Result。
|
|
1253
|
+
/// ---
|
|
1254
|
+
/// purpose: team-agent watch 的主循环:反复增量收集、输出、休眠
|
|
1255
|
+
/// params:
|
|
1256
|
+
/// workspace: workspace 根
|
|
1257
|
+
/// team: 只看这个 team;None 表示不过滤
|
|
1258
|
+
/// interval_sec: 轮询间隔;非有限值或非正数时回落到内置默认
|
|
1259
|
+
/// output: 输出回调,注入以便测试;本函数自己不写 stdout
|
|
1260
|
+
/// returns: 循环结束时为 Ok。这是个长跑循环,正常运行期间不返回
|
|
1261
|
+
/// errors: 打开 message store 或某轮收集失败时返回 WatchError
|
|
1262
|
+
/// ---
|
|
1090
1263
|
pub fn run_watch(
|
|
1091
1264
|
workspace: &WorkspacePath,
|
|
1092
1265
|
team: Option<&str>,
|
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: coordinator 子系统装配面——声明各子模块并把它们的公开面重导出到 crate::coordinator
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: run_daemon
|
|
6
|
+
//! what: per-workspace daemon 进程入口(主循环 + 退避)
|
|
7
|
+
//! - name: coordinator_health
|
|
8
|
+
//! what: pid/metadata/schema 三合一健康判定
|
|
9
|
+
//! - name: start_coordinator
|
|
10
|
+
//! what: 幂等启动 daemon 子进程
|
|
11
|
+
//! - name: stop_coordinator
|
|
12
|
+
//! what: 停 daemon 并清 pid/meta
|
|
13
|
+
//! - name: run_watch
|
|
14
|
+
//! what: team-agent watch 的只读事件流主循环
|
|
15
|
+
//! - name: detect_whole_team_gone
|
|
16
|
+
//! what: 不依赖 coordinator 存活的整队消失判定
|
|
17
|
+
//! depends:
|
|
18
|
+
//! - crate::state
|
|
19
|
+
//! - crate::message_store
|
|
20
|
+
//! - crate::messaging
|
|
21
|
+
//! - crate::transport
|
|
22
|
+
//! - crate::provider
|
|
23
|
+
//! - crate::leader
|
|
24
|
+
//! - crate::event_log
|
|
25
|
+
//! boundary:
|
|
26
|
+
//! - 不实现消息投递与结果回收本体(在 crate::messaging,本模块只按固定顺序调用)
|
|
27
|
+
//! - 不直接依赖任何 provider client crate,provider 一律经 ProviderAdapter trait
|
|
28
|
+
//! - 不做物理注入/键盘写入,注入归 crate::transport
|
|
29
|
+
//! - 无 pending obligation 时不注入任何探索性 prompt
|
|
30
|
+
//! maturity: wired
|
|
31
|
+
//! ---
|
|
1
32
|
//!
|
|
2
33
|
//! step 12 · coordinator — daemon lifecycle / single-tick orchestration SKELETON (ROUND-0).
|
|
3
34
|
//!
|