@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
@@ -93,6 +93,27 @@ fn turn_verification_serde_snake_case_byte_locked() {
93
93
  }
94
94
  }
95
95
 
96
+ #[test]
97
+ fn turn_verification_wire_never_defaults_started() {
98
+ use crate::transport::turn_verification_wire;
99
+ assert_eq!(
100
+ turn_verification_wire(TurnVerification::NotYetObserved),
101
+ "not_yet_observed"
102
+ );
103
+ assert_eq!(
104
+ turn_verification_wire(TurnVerification::LeaderNewTurnBoundaryMissing),
105
+ "leader_new_turn_boundary_missing"
106
+ );
107
+ assert_eq!(
108
+ turn_verification_wire(TurnVerification::LeaderNewTurnBoundaryVerified),
109
+ "leader_new_turn_boundary_verified"
110
+ );
111
+ assert_ne!(
112
+ turn_verification_wire(TurnVerification::NotYetObserved),
113
+ "leader_new_turn_boundary_verified"
114
+ );
115
+ }
116
+
96
117
  #[test]
97
118
  fn submit_verification_wire_strings_byte_locked() {
98
119
  // 09-transport.md 表 §39 + tmux_io.py:64/215-221 + tmux_prompt.py:304/313。
@@ -349,11 +370,12 @@ fn inject_empty_payload_reports_empty_text_send_keys_and_turn_not_required() {
349
370
  fn empty_inject_is_single_direct_send_keys_never_buffer() {
350
371
  // 命令构造 golden(STEP-9):空文本禁走 buffer,只发一条 send-keys 提交键
351
372
  // (tmux_io.py:42 —— tmux 拒空 buffer 会卡 trust prompt)。
352
- // golden empty_inject_calls 仅 1 条:send-keys -t %7 Enter。RED via stub。
373
+ // golden empty_inject_calls 仅 1 条:send-keys -t %7 C-m(锁定阳性 CR)。
353
374
  assert_eq!(
354
375
  tmux_empty_inject_argv(&PaneId::new("%7"), Key::Enter),
355
- vec!["tmux", "send-keys", "-t", "%7", "Enter"]
376
+ vec!["tmux", "send-keys", "-t", "%7", "C-m"]
356
377
  );
378
+ assert_eq!(crate::transport::tmux_submit_key_name(Key::Enter), "C-m");
357
379
  }
358
380
 
359
381
  #[test]
@@ -519,6 +541,10 @@ fn cancel_mode_is_noop_on_non_tmux_backends() {
519
541
  tmux_cancel_mode_argv(&PaneId::new("%7"), PaneMode::Unknown),
520
542
  vec!["tmux", "send-keys", "-t", "%7", "-X", "cancel"]
521
543
  );
544
+ assert_eq!(
545
+ tmux_close_bracketed_paste_argv(&PaneId::new("%7")),
546
+ vec!["tmux", "send-keys", "-t", "%7", "-l", "\u{1b}[201~"]
547
+ );
522
548
  }
523
549
 
524
550
  // ════════════════════════════════════════════════════════════════════════
@@ -1,3 +1,43 @@
1
+ //! ---
2
+ //! purpose: 终端控制面的 Transport trait、typed 载荷,以及 tmux argv 的纯构造函数
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: Transport
6
+ //! what: 后端无关的 spawn / inject / capture / query / liveness / lifecycle 接口;daemon 可达方法一律返回 Result<_, TransportError>
7
+ //! - name: SetEnvOutcome
8
+ //! what: set-env 的能力性拒绝走 typed 变体,不走 Err、不 panic
9
+ //! - name: AttachOutcome
10
+ //! what: attach 的能力性拒绝走 typed 变体,不走 Err、不 panic
11
+ //! - name: tmux_spawn_argv
12
+ //! what: spawn argv 的唯一构造点(first→new-session / 否则 new-window),纯函数可 golden 锁
13
+ //! - name: tmux_inject_text_argv
14
+ //! what: 文本注入三步 argv(装 buffer→paste→删 buffer)的唯一构造点
15
+ //! - name: tmux_send_submit_argv
16
+ //! what: 提交键 argv 的唯一构造点,Enter 锁定为 C-m
17
+ //! - name: tmux_capture_argv
18
+ //! what: capture-pane argv 的唯一构造点
19
+ //! - name: tmux_query_argv
20
+ //! what: display-message 单字段查询 argv 的唯一构造点
21
+ //! - name: tmux_cancel_mode_argv
22
+ //! what: 按 pane mode 分派退出键的 argv 构造点
23
+ //! - name: InjectReport
24
+ //! what: 阶段化注入报告(stage_reached / inject_verification / submit_verification / turn_verification / attempts / submit_diagnostics)
25
+ //! - name: submit_verification_wire
26
+ //! what: SubmitVerification 落 JSON 的唯一拼写来源
27
+ //! - name: turn_verification_wire
28
+ //! what: TurnVerification 落 JSON 的唯一拼写来源
29
+ //! depends:
30
+ //! - crate::model::enums::PaneLiveness
31
+ //! - serde
32
+ //! - thiserror
33
+ //! boundary:
34
+ //! - 不做投递语义(发给谁 / 为何发 / 可见性启发式)——那在 transport 之上的 delivery 层
35
+ //! - 本文件不执行 I/O;真正跑 argv 的是 tmux_backend
36
+ //! - turn_verification 不是投递闸门(Gap42),not_yet_observed 也算成功
37
+ //! - 不透传后端字面键名:抽象 Key 由各后端自行翻译
38
+ //! - submit_diagnostics 不上 typed wire,只在进程内传递
39
+ //! maturity: wired
40
+ //! ---
1
41
  //!
2
42
  //! step 9 · transport — 控制面 trait + typed payload(ROUND-0 SKELETON).
3
43
  //!
@@ -46,9 +86,21 @@ pub use crate::model::enums::PaneLiveness;
46
86
  pub struct PaneId(pub String);
47
87
 
48
88
  impl PaneId {
89
+ /// ---
90
+ /// purpose: 把已知的 pane 标识字符串包成 PaneId newtype
91
+ /// params:
92
+ /// s: 后端给出的 pane 标识原文;不做校验、不归一化
93
+ /// returns: 与入参字节相同的 PaneId(serde transparent)
94
+ /// boundary: 不校验格式、不区分 tmux/wezterm/conpty 方言;混入 window/session 名由类型系统在调用点挡
95
+ /// ---
49
96
  pub fn new(s: impl Into<String>) -> Self {
50
97
  Self(s.into())
51
98
  }
99
+ /// ---
100
+ /// purpose: 借出 pane 标识的原始字符串,供 argv 拼装
101
+ /// returns: 内部字符串的借用,与构造时字节一致
102
+ /// boundary: 不做转义、不加引号;shell 引用由调用方负责
103
+ /// ---
52
104
  pub fn as_str(&self) -> &str {
53
105
  &self.0
54
106
  }
@@ -72,9 +124,21 @@ impl From<&str> for PaneId {
72
124
  pub struct SessionName(pub String);
73
125
 
74
126
  impl SessionName {
127
+ /// ---
128
+ /// purpose: 把 tmux session 名包成 SessionName newtype
129
+ /// params:
130
+ /// s: session 名原文;不做校验、不归一化
131
+ /// returns: 与入参字节相同的 SessionName(serde transparent)
132
+ /// boundary: 不校验 session 是否存在(那是 Transport::has_session)
133
+ /// ---
75
134
  pub fn new(s: impl Into<String>) -> Self {
76
135
  Self(s.into())
77
136
  }
137
+ /// ---
138
+ /// purpose: 借出 session 名原文,供 argv 拼装
139
+ /// returns: 内部字符串的借用,与构造时字节一致
140
+ /// boundary: 不做转义、不加引号
141
+ /// ---
78
142
  pub fn as_str(&self) -> &str {
79
143
  &self.0
80
144
  }
@@ -98,9 +162,21 @@ impl From<&str> for SessionName {
98
162
  pub struct WindowName(pub String);
99
163
 
100
164
  impl WindowName {
165
+ /// ---
166
+ /// purpose: 把 tmux window 名包成 WindowName newtype
167
+ /// params:
168
+ /// s: window 名原文;不做校验、不归一化
169
+ /// returns: 与入参字节相同的 WindowName(serde transparent)
170
+ /// boundary: 不校验 window 是否存在(那是 Transport::list_windows)
171
+ /// ---
101
172
  pub fn new(s: impl Into<String>) -> Self {
102
173
  Self(s.into())
103
174
  }
175
+ /// ---
176
+ /// purpose: 借出 window 名原文,供 argv 拼装
177
+ /// returns: 内部字符串的借用,与构造时字节一致
178
+ /// boundary: 不做转义、不加引号
179
+ /// ---
104
180
  pub fn as_str(&self) -> &str {
105
181
  &self.0
106
182
  }
@@ -147,6 +223,11 @@ pub enum InjectPayload {
147
223
  }
148
224
 
149
225
  impl InjectPayload {
226
+ /// ---
227
+ /// purpose: 取出载荷正文,把「有正文」与「空载荷」在类型上分开
228
+ /// returns: Text / TextSkipConsumptionPoll 返回 Some(正文);Empty 返回 None
229
+ /// boundary: 不区分两种带正文的变体(要区分用 skip_consumption_poll);不裁剪、不脱敏
230
+ /// ---
150
231
  pub fn text(&self) -> Option<&str> {
151
232
  match self {
152
233
  Self::Text(text) | Self::TextSkipConsumptionPoll(text) => Some(text),
@@ -154,6 +235,11 @@ impl InjectPayload {
154
235
  }
155
236
  }
156
237
 
238
+ /// ---
239
+ /// purpose: 标出「收件 pane 不消费 provider 回合」的载荷,后端据此跳过 post-Enter 消费轮询
240
+ /// returns: 仅 TextSkipConsumptionPoll 为 true
241
+ /// boundary: 只表达「不轮询消费」这一件事,不表达投递成功;调用方不得据此直接判 delivered
242
+ /// ---
157
243
  pub fn skip_consumption_poll(&self) -> bool {
158
244
  matches!(self, Self::TextSkipConsumptionPoll(_))
159
245
  }
@@ -832,6 +918,14 @@ pub enum PaneMode {
832
918
  /// 把抽象 `Key` 翻译成 tmux send-keys 字面键名(各后端翻译,不透传字面量;§gap-5)。
833
919
  /// tmux:Enter/Up/Down/Left/Right/数字字符/C-c;CancelMode 不是单键(走 cancel_mode_argv)。
834
920
  /// 真相源:codex.py:266 `send-keys -t %7 Down Enter`、tmux send-keys 键名约定。
921
+ /// 菜单/方向键序列继续用这里的 `Enter`。投递提交键走 [`tmux_submit_key_name`]。
922
+ /// ---
923
+ /// purpose: 抽象 Key 到 tmux send-keys 字面键名的翻译表
924
+ /// params:
925
+ /// key: 抽象按键;CancelMode 与非数字 Char 无单键拼写
926
+ /// returns: tmux 键名;CancelMode 与未覆盖的 Char 返回空串,由调用方过滤掉
927
+ /// boundary: 不构 argv、不发键;CancelMode 的真正出口是 tmux_cancel_mode_argv;提交键拼写不在这里(见 tmux_submit_key_name)
928
+ /// ---
835
929
  pub fn tmux_key_name(key: Key) -> &'static str {
836
930
  match key {
837
931
  Key::Enter => "Enter",
@@ -860,6 +954,14 @@ pub fn tmux_key_name(key: Key) -> &'static str {
860
954
 
861
955
  /// `send-keys -t <target> <k1> <k2> ...`(键名经 `tmux_key_name` 翻译)。
862
956
  /// golden:`[Down, Enter]` → `["tmux","send-keys","-t","%7","Down","Enter"]`(codex.py:266)。
957
+ /// ---
958
+ /// purpose: 构造按键序列的 send-keys argv(菜单/方向键路径)
959
+ /// params:
960
+ /// pane: 目标 pane 标识,直接进 -t
961
+ /// keys: 按顺序翻译成键名;翻译为空串的键被丢弃,不占位
962
+ /// returns: 完整 argv;keys 全为空串时退化成只有前缀的 send-keys(不会报错)
963
+ /// boundary: 纯构造,不执行;不加 socket 参数(-L/-S 由 tmux_backend 的 run chokepoint 补);此处的 Enter 是菜单键名,不是提交拼写
964
+ /// ---
863
965
  pub fn tmux_send_keys_argv(pane: &PaneId, keys: &[Key]) -> Vec<String> {
864
966
  let mut argv = vec![
865
967
  "tmux".to_string(),
@@ -877,8 +979,74 @@ pub fn tmux_send_keys_argv(pane: &PaneId, keys: &[Key]) -> Vec<String> {
877
979
  argv
878
980
  }
879
981
 
982
+ /// 投递提交键的 tmux 拼写。
983
+ ///
984
+ /// ledger.p0enter 锁定对:`C-j`/LF 5/5 不成回合,`C-m`/CR 5/5 成回合。
985
+ /// `send-keys Enter` 在 raw PTY 上也是 `0d`,但提交路径只发锁定阳性名
986
+ /// `C-m`,避免落到 LF 族。菜单 `send_keys([Down, Enter])` 不走这里。
987
+ /// ---
988
+ /// purpose: 投递提交键的 tmux 拼写:Enter 锁定为 C-m(CR),其余键回落通用翻译
989
+ /// params:
990
+ /// key: 提交键;只有 Enter 有专门拼写
991
+ /// returns: 提交用键名;非 Enter 与 tmux_key_name 同值(含空串情形)
992
+ /// boundary: 只管拼写,不管何时提交、提交是否成功;不发送、不验证
993
+ /// ---
994
+ pub fn tmux_submit_key_name(key: Key) -> &'static str {
995
+ match key {
996
+ Key::Enter => "C-m",
997
+ other => tmux_key_name(other),
998
+ }
999
+ }
1000
+
1001
+ /// 投递提交:`send-keys -t <pane> <submit-spelling>`。
1002
+ /// ---
1003
+ /// purpose: 构造投递提交键的 send-keys argv
1004
+ /// params:
1005
+ /// pane: 目标 pane 标识
1006
+ /// submit: 提交键;经 tmux_submit_key_name 取拼写
1007
+ /// returns: 单条 argv,末位为提交键拼写
1008
+ /// boundary: 纯构造,不执行;tmux 对该 argv 返回 rc=0 不等于回合已提交(copy-mode 会吞键),真伪判定在 tmux_backend 的消费验证
1009
+ /// ---
1010
+ pub fn tmux_send_submit_argv(pane: &PaneId, submit: Key) -> Vec<String> {
1011
+ vec![
1012
+ "tmux".to_string(),
1013
+ "send-keys".to_string(),
1014
+ "-t".to_string(),
1015
+ pane.as_str().to_string(),
1016
+ tmux_submit_key_name(submit).to_string(),
1017
+ ]
1018
+ }
1019
+
1020
+ /// 闭合未完成的 bracketed paste:字面 CSI `ESC [ 201 ~`。
1021
+ /// 不是 `Escape` 键(E55:重试只重按回车,绝不送 Escape/C-c)。
1022
+ /// ---
1023
+ /// purpose: 构造「字面写入 CSI 201~」的 argv,闭合 composer 里未收尾的 bracketed paste
1024
+ /// params:
1025
+ /// pane: 目标 pane 标识
1026
+ /// returns: 带 -l 的 send-keys argv,写入的是字面转义序列而非键名
1027
+ /// boundary: 绝不用 Escape / Ctrl-C 代替(E55 红线);纯构造,不执行;不判断 pane 当前是否真处于 paste 中
1028
+ /// ---
1029
+ pub fn tmux_close_bracketed_paste_argv(pane: &PaneId) -> Vec<String> {
1030
+ vec![
1031
+ "tmux".to_string(),
1032
+ "send-keys".to_string(),
1033
+ "-t".to_string(),
1034
+ pane.as_str().to_string(),
1035
+ "-l".to_string(),
1036
+ "\u{1b}[201~".to_string(),
1037
+ ]
1038
+ }
1039
+
880
1040
  /// CancelMode 在 tmux 上按 pane mode 分派退出键(tmux_io.py:419-426)。
881
1041
  /// golden:Copy→`-X cancel`,Tree/View→`q`,Client→`d`,Unknown→`-X cancel`(+warn)。
1042
+ /// ---
1043
+ /// purpose: 按 pane 当前 mode 分派退出键,构造离开特殊 mode 的 argv
1044
+ /// params:
1045
+ /// pane: 目标 pane 标识
1046
+ /// mode: 已探到的 pane mode;Unknown 保守走 -X cancel
1047
+ /// returns: 单条 send-keys argv,尾部按 mode 取 -X cancel / q / d
1048
+ /// boundary: 纯构造,不执行、不探测 mode(探测在 tmux_backend);不保证 mode 真的退出了,调用方需复查
1049
+ /// ---
882
1050
  pub fn tmux_cancel_mode_argv(pane: &PaneId, mode: PaneMode) -> Vec<String> {
883
1051
  let mut argv = vec![
884
1052
  "tmux".to_string(),
@@ -900,6 +1068,14 @@ pub fn tmux_cancel_mode_argv(pane: &PaneId, mode: PaneMode) -> Vec<String> {
900
1068
  /// CaptureRange → `capture-pane -p -S <spec> -t <target>`。
901
1069
  /// golden:`Tail(40)` → `-S -40`(tmux_prompt.py:149/tmux_io.py:410);
902
1070
  /// `Full` → `-S -`(runtime.py:519)。
1071
+ /// ---
1072
+ /// purpose: 把 CaptureRange 翻成 capture-pane 的起止参数
1073
+ /// params:
1074
+ /// pane: 目标 pane 标识
1075
+ /// range: Tail(n) 取倒数 n 行 -S -n;Head(n) 取 -S 0 并追加 -E n-1;Full 取 -S -
1076
+ /// returns: 完整 capture-pane argv;Head(0) 时 -E 饱和到 0(不会下溢)
1077
+ /// boundary: 纯构造,不执行、不解析输出;抓回来的文本要经 normalize_capture 才交给识别层
1078
+ /// ---
903
1079
  pub fn tmux_capture_argv(pane: &PaneId, range: CaptureRange) -> Vec<String> {
904
1080
  let spec = match range {
905
1081
  CaptureRange::Tail(lines) => format!("-{lines}"),
@@ -924,6 +1100,14 @@ pub fn tmux_capture_argv(pane: &PaneId, range: CaptureRange) -> Vec<String> {
924
1100
  /// PaneField → `display-message -p -t <target> [-F] <fmt>`。
925
1101
  /// golden:PaneWidth → `-F '#{pane_width}'`(delivery.py:34);
926
1102
  /// PaneMode → `'#{pane_mode}'`(tmux_io.py:403);PaneId → `'#{pane_id}'`(state.py:346)。
1103
+ /// ---
1104
+ /// purpose: 把 PaneField 翻成 display-message 的单字段查询 argv
1105
+ /// params:
1106
+ /// pane: 目标 pane 标识
1107
+ /// field: 待查字段;PaneMode 走不带 -F 的形式,其余字段加 -F
1108
+ /// returns: 完整 display-message argv,末位是 tmux 格式串
1109
+ /// boundary: 纯构造,不执行、不解析;查询失败与「字段不适用」的区分在 Transport::query 的 Option 返回值上
1110
+ /// ---
927
1111
  pub fn tmux_query_argv(pane: &PaneId, field: PaneField) -> Vec<String> {
928
1112
  let fmt = match field {
929
1113
  PaneField::PaneId => "#{pane_id}",
@@ -953,6 +1137,16 @@ pub fn tmux_query_argv(pane: &PaneId, field: PaneField) -> Vec<String> {
953
1137
  /// first → `new-session -d -P -F #{pane_id} -s <s> -n <w> sh -lc <cmd>`
954
1138
  /// into → `new-window -d -P -F #{pane_id} -t <s> -n <w> sh -lc <cmd>`
955
1139
  /// `argv` 被组装成单条 `sh -lc` 命令字符串(provider 启动行)。
1140
+ /// ---
1141
+ /// purpose: 构造 spawn argv:首个席位用 new-session,后续席位用 new-window
1142
+ /// params:
1143
+ /// session: 目标 session 名
1144
+ /// window: 新窗口名
1145
+ /// command: 已拼好的单条 shell 命令行,原样进 sh -lc(本函数不做引用/转义)
1146
+ /// first: true 走 new-session,false 走 new-window
1147
+ /// returns: 完整 argv;两支都带 -d(后台创建,不抢客户端焦点)与 -P -F 使 tmux 回打 pane_id
1148
+ /// boundary: 纯构造,不执行、不校验 session 是否已存在;command 的 shell 引用由调用方(tmux_backend 的 shell wrapper)负责
1149
+ /// ---
956
1150
  pub fn tmux_spawn_argv(
957
1151
  session: &SessionName,
958
1152
  window: &WindowName,
@@ -1007,6 +1201,16 @@ pub fn tmux_spawn_argv(
1007
1201
  /// set-buffer → `set-buffer -b <buf> <text>`
1008
1202
  /// paste-buffer→ `paste-buffer -t <target> -b <buf> -p`(-p = bracketed)
1009
1203
  /// delete-buffer→`delete-buffer -b <buf>`
1204
+ /// ---
1205
+ /// purpose: 构造文本注入的三步 argv 序列:装 buffer → 粘到 pane → 删 buffer
1206
+ /// params:
1207
+ /// pane: 目标 pane 标识
1208
+ /// buffer_name: 本次注入独占的 tmux buffer 名,三步共用同一个名字
1209
+ /// text: 待注入正文;长度 >= 16 KiB 改走 load-buffer - (由 stdin 喂),否则 set-buffer 直传
1210
+ /// bracketed: true 时 paste-buffer 加 -p(bracketed paste)
1211
+ /// returns: 有序的三条 argv:[load 或 set, paste, delete];调用方必须按序执行
1212
+ /// boundary: 纯构造,不执行、不发提交键;空文本禁走这条路径(见 tmux_empty_inject_argv);不保证 buffer 名唯一,去重由调用方负责
1213
+ /// ---
1010
1214
  pub fn tmux_inject_text_argv(
1011
1215
  pane: &PaneId,
1012
1216
  buffer_name: &str,
@@ -1056,13 +1260,29 @@ pub fn tmux_inject_text_argv(
1056
1260
 
1057
1261
  /// 空文本 inject:纯 `send-keys -t <target> <submit_key>`,**禁** buffer 路径
1058
1262
  /// (tmux 拒空 buffer 会卡 trust prompt;tmux_io.py:42)。
1263
+ /// `Key::Enter` 在此译成锁定阳性 `C-m`,不是菜单用的 `Enter`。
1264
+ /// ---
1265
+ /// purpose: 空载荷注入(唤醒/ping)的 argv:只发提交键,不碰 buffer
1266
+ /// params:
1267
+ /// pane: 目标 pane 标识
1268
+ /// submit: 提交键;转发给 tmux_send_submit_argv 取拼写
1269
+ /// returns: 与 tmux_send_submit_argv 完全相同的单条 argv
1270
+ /// boundary: 禁走 buffer 路径(tmux 拒空 buffer 会卡 trust prompt);纯构造,不执行、不做任何提交验证
1271
+ /// ---
1059
1272
  pub fn tmux_empty_inject_argv(pane: &PaneId, submit: Key) -> Vec<String> {
1060
- tmux_send_keys_argv(pane, &[submit])
1273
+ tmux_send_submit_argv(pane, submit)
1061
1274
  }
1062
1275
 
1063
1276
  /// capture 出口规范化(§4b,design line 399-400):逐行 rstrip 行尾空白 +
1064
1277
  /// `\xa0`(NBSP)→`\x20`,保留 box-drawing。recognizer 不为后端开分支。
1065
1278
  /// golden:`"line one \nbusy\xa0marker \n \n"` → `"line one\nbusy marker\n\n"`。
1279
+ /// ---
1280
+ /// purpose: capture 出口的统一规范化,让识别层不必为后端开分支
1281
+ /// params:
1282
+ /// raw: 后端抓回的原始屏幕文本
1283
+ /// returns: 逐行去掉行尾空白、NBSP 归一为普通空格的文本;换行位置与行数不变
1284
+ /// boundary: 只做这两件事——不剥 ANSI 转义、不裁剪行数、不脱敏、不改 box-drawing 字符
1285
+ /// ---
1066
1286
  pub fn normalize_capture(raw: &str) -> String {
1067
1287
  raw.replace('\u{a0}', " ")
1068
1288
  .split_inclusive('\n')
@@ -1083,6 +1303,16 @@ pub fn normalize_capture(raw: &str) -> String {
1083
1303
  /// 真相源:`enter_sent_without_placeholder_check` /
1084
1304
  /// `pasted_content_prompt_absent_after_submit` / `send_keys_failed` /
1085
1305
  /// `Enter_sent_after_visible_token`(submit_key 字面)。
1306
+ /// ---
1307
+ /// purpose: SubmitVerification 判定值落 JSON / 事件的唯一拼写来源
1308
+ /// params:
1309
+ /// v: 后端给出的提交验证判定
1310
+ /// returns: 稳定 wire 字符串;KeySentAfterVisibleToken 由 tmux_key_name 拼成 <键名>_sent_after_visible_token
1311
+ /// boundary:
1312
+ /// - 只翻译不判定:不把 SubmitConsumptionUnverified 折成成功值,也不合并任何两个变体
1313
+ /// - 名与语义已知脱节:enter_sent_without_placeholder_check 实际含义是「消费已确认或跳过消费轮询」,读日志时不可按字面理解成未验证
1314
+ /// - 是否算 delivered 不在这里裁定,由 delivery 层的判定表决定
1315
+ /// ---
1086
1316
  pub fn submit_verification_wire(v: SubmitVerification) -> String {
1087
1317
  match v {
1088
1318
  SubmitVerification::EnterSentWithoutPlaceholderCheck => {
@@ -1104,6 +1334,23 @@ pub fn submit_verification_wire(v: SubmitVerification) -> String {
1104
1334
  }
1105
1335
  }
1106
1336
 
1337
+ /// ---
1338
+ /// purpose: TurnVerification 的 send JSON / persist 用词
1339
+ /// params:
1340
+ /// v: 回合观测判定
1341
+ /// returns: 与 serde snake_case 一致的固定拼写
1342
+ /// contract: 与 serde snake_case 一致;未知必须是 not_yet_observed,不得写成 verified
1343
+ /// boundary: 不是投递闸门(Gap42);只翻译不判定
1344
+ /// ---
1345
+ pub fn turn_verification_wire(v: TurnVerification) -> &'static str {
1346
+ match v {
1347
+ TurnVerification::LeaderNewTurnBoundaryVerified => "leader_new_turn_boundary_verified",
1348
+ TurnVerification::LeaderNewTurnBoundaryMissing => "leader_new_turn_boundary_missing",
1349
+ TurnVerification::NotYetObserved => "not_yet_observed",
1350
+ TurnVerification::NotRequired => "not_required",
1351
+ }
1352
+ }
1353
+
1107
1354
  // ─────────────────────────────────────────────────────────────────────────────
1108
1355
  // RED contracts — step 9 transport(ROUND-0).
1109
1356
  //