@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.
Files changed (164) hide show
  1. package/Cargo.lock +8 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +1 -0
  4. package/crates/team-agent/src/cli/adapters.rs +5 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +82 -0
  6. package/crates/team-agent/src/cli/emit.rs +3 -3
  7. package/crates/team-agent/src/cli/grok_slot.rs +299 -0
  8. package/crates/team-agent/src/cli/leader.rs +21 -7
  9. package/crates/team-agent/src/cli/leaders.rs +125 -1
  10. package/crates/team-agent/src/cli/mod.rs +20 -0
  11. package/crates/team-agent/src/cli/send/presentation.rs +7 -1
  12. package/crates/team-agent/src/cli/spec.rs +2 -0
  13. package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
  14. package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
  15. package/crates/team-agent/src/cli/tests/base.rs +26 -3
  16. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
  17. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
  18. package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
  19. package/crates/team-agent/src/communication_mode/mod.rs +6 -4
  20. package/crates/team-agent/src/compiler.rs +59 -0
  21. package/crates/team-agent/src/coordinator/backoff.rs +55 -0
  22. package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
  23. package/crates/team-agent/src/coordinator/health.rs +173 -0
  24. package/crates/team-agent/src/coordinator/mod.rs +31 -0
  25. package/crates/team-agent/src/coordinator/orphan.rs +31 -0
  26. package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
  27. package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
  28. package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
  29. package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
  30. package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
  31. package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
  32. package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
  33. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
  34. package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
  35. package/crates/team-agent/src/coordinator/tick.rs +133 -22
  36. package/crates/team-agent/src/coordinator/types.rs +49 -0
  37. package/crates/team-agent/src/db/message_store.rs +39 -5
  38. package/crates/team-agent/src/layout/worker_env.rs +20 -3
  39. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  40. package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
  41. package/crates/team-agent/src/leader/registry.rs +65 -0
  42. package/crates/team-agent/src/leader/start.rs +920 -63
  43. package/crates/team-agent/src/lifecycle/display.rs +56 -0
  44. package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
  45. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -12
  46. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +62 -0
  47. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +59 -0
  48. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
  49. package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
  50. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
  51. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -456
  52. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
  53. package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
  54. package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
  55. package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
  56. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
  57. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
  58. package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
  59. package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
  60. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
  61. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
  62. package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
  63. package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
  64. package/crates/team-agent/src/lifecycle/launch/spawn.rs +62 -2
  65. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +139 -0
  66. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
  67. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
  68. package/crates/team-agent/src/lifecycle/launch.rs +99 -29
  69. package/crates/team-agent/src/lifecycle/lock.rs +42 -1
  70. package/crates/team-agent/src/lifecycle/mod.rs +11 -0
  71. package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
  72. package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
  73. package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
  74. package/crates/team-agent/src/lifecycle/restart/agent.rs +124 -0
  75. package/crates/team-agent/src/lifecycle/restart/common.rs +369 -11
  76. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
  77. package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
  78. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
  79. package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
  80. package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
  81. package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
  82. package/crates/team-agent/src/lifecycle/restart.rs +29 -0
  83. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
  84. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
  85. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
  86. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
  87. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
  88. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
  89. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
  90. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
  91. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
  92. package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
  93. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
  94. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
  95. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
  96. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
  97. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
  98. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
  99. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
  100. package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
  101. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
  102. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
  103. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +2 -0
  104. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
  105. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
  106. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
  107. package/crates/team-agent/src/lifecycle/tests.rs +23 -13
  108. package/crates/team-agent/src/lifecycle/types.rs +61 -0
  109. package/crates/team-agent/src/lifecycle/worker_command_context.rs +85 -11
  110. package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
  111. package/crates/team-agent/src/mcp_server/tests.rs +196 -2
  112. package/crates/team-agent/src/messaging/delivery.rs +405 -40
  113. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  114. package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
  115. package/crates/team-agent/src/messaging/results.rs +4 -0
  116. package/crates/team-agent/src/messaging/send.rs +34 -0
  117. package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
  118. package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
  119. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
  120. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  121. package/crates/team-agent/src/messaging/types.rs +4 -1
  122. package/crates/team-agent/src/model/enums.rs +14 -5
  123. package/crates/team-agent/src/model/permissions.rs +3 -0
  124. package/crates/team-agent/src/os_probe.rs +73 -2
  125. package/crates/team-agent/src/provider/adapter.rs +231 -4
  126. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
  127. package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
  128. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  129. package/crates/team-agent/src/provider/bypass_flags.rs +46 -2
  130. package/crates/team-agent/src/provider/classify.rs +4 -2
  131. package/crates/team-agent/src/provider/faults.rs +2 -1
  132. package/crates/team-agent/src/provider/mod.rs +11 -0
  133. package/crates/team-agent/src/provider/session/capture.rs +158 -20
  134. package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
  135. package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
  136. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
  137. package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
  138. package/crates/team-agent/src/provider/session/mod.rs +18 -0
  139. package/crates/team-agent/src/provider/session/resume.rs +57 -0
  140. package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
  141. package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
  142. package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
  143. package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
  144. package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
  145. package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
  146. package/crates/team-agent/src/provider/session_scan.rs +8 -0
  147. package/crates/team-agent/src/provider/submit_now.rs +94 -0
  148. package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
  149. package/crates/team-agent/src/provider/types.rs +4 -2
  150. package/crates/team-agent/src/provider/wire.rs +23 -1
  151. package/crates/team-agent/src/state/persist.rs +301 -8
  152. package/crates/team-agent/src/state/repository.rs +5 -0
  153. package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
  154. package/crates/team-agent/src/tmux_backend.rs +1064 -75
  155. package/crates/team-agent/src/transport/tests/wire.rs +28 -2
  156. package/crates/team-agent/src/transport.rs +248 -1
  157. package/npm/install.mjs +129 -73
  158. package/package.json +4 -4
  159. package/skills/team-agent/SKILL.md +33 -238
  160. package/skills/team-agent/command-coverage.json +31 -0
  161. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
  162. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
  163. package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
  164. 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
  //!