@team-agent/installer 0.5.42 → 0.5.44

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 (156) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +129 -54
  4. package/crates/team-agent/src/cli/diagnose.rs +1 -2
  5. package/crates/team-agent/src/cli/emit.rs +1 -7
  6. package/crates/team-agent/src/cli/helpers.rs +3 -1
  7. package/crates/team-agent/src/cli/leader.rs +2 -1
  8. package/crates/team-agent/src/cli/mod.rs +126 -16
  9. package/crates/team-agent/src/cli/named_address.rs +14 -5
  10. package/crates/team-agent/src/cli/profile.rs +19 -7
  11. package/crates/team-agent/src/cli/send.rs +9 -3
  12. package/crates/team-agent/src/cli/status.rs +8 -30
  13. package/crates/team-agent/src/cli/status_port.rs +1341 -1272
  14. package/crates/team-agent/src/cli/tests/base.rs +738 -660
  15. package/crates/team-agent/src/cli/tests/compile.rs +45 -18
  16. package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
  21. package/crates/team-agent/src/cli/tests/mod.rs +17 -7
  22. package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
  23. package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
  25. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
  26. package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
  27. package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
  28. package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
  29. package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
  30. package/crates/team-agent/src/codex_app_server.rs +2 -5
  31. package/crates/team-agent/src/compiler/tests.rs +139 -33
  32. package/crates/team-agent/src/compiler.rs +55 -22
  33. package/crates/team-agent/src/conpty/backend.rs +23 -33
  34. package/crates/team-agent/src/coordinator/backoff.rs +2 -7
  35. package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
  36. package/crates/team-agent/src/coordinator/health.rs +46 -31
  37. package/crates/team-agent/src/coordinator/mod.rs +3 -3
  38. package/crates/team-agent/src/coordinator/orphan.rs +22 -10
  39. package/crates/team-agent/src/coordinator/steps/abnormal.rs +51 -56
  40. package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
  41. package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
  42. package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
  43. package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
  44. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
  45. package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
  46. package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
  47. package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
  48. package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
  49. package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
  50. package/crates/team-agent/src/db/message_store.rs +138 -30
  51. package/crates/team-agent/src/db/migration.rs +249 -61
  52. package/crates/team-agent/src/db/schema.rs +303 -82
  53. package/crates/team-agent/src/diagnose/comms.rs +9 -2
  54. package/crates/team-agent/src/diagnose/mod.rs +1 -3
  55. package/crates/team-agent/src/diagnose/orphans.rs +79 -61
  56. package/crates/team-agent/src/event_log.rs +70 -16
  57. package/crates/team-agent/src/layout/manager.rs +15 -4
  58. package/crates/team-agent/src/layout/mod.rs +4 -4
  59. package/crates/team-agent/src/layout/overlay.rs +10 -3
  60. package/crates/team-agent/src/layout/placement.rs +5 -1
  61. package/crates/team-agent/src/layout/recovery.rs +4 -2
  62. package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
  63. package/crates/team-agent/src/layout/sessions.rs +17 -9
  64. package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
  65. package/crates/team-agent/src/layout/worker_env.rs +87 -19
  66. package/crates/team-agent/src/leader/helpers.rs +7 -1
  67. package/crates/team-agent/src/leader/lease.rs +199 -89
  68. package/crates/team-agent/src/leader/owner_bind.rs +55 -22
  69. package/crates/team-agent/src/leader/provider_attribution.rs +25 -6
  70. package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
  71. package/crates/team-agent/src/leader/rediscover.rs +74 -25
  72. package/crates/team-agent/src/leader/registry.rs +1 -1
  73. package/crates/team-agent/src/leader/start.rs +75 -54
  74. package/crates/team-agent/src/leader/takeover.rs +46 -11
  75. package/crates/team-agent/src/leader/tests/basics.rs +320 -167
  76. package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
  77. package/crates/team-agent/src/leader/tests/identity.rs +428 -356
  78. package/crates/team-agent/src/leader/tests/idle.rs +285 -254
  79. package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
  80. package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
  81. package/crates/team-agent/src/leader/tests/mod.rs +115 -99
  82. package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
  83. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
  84. package/crates/team-agent/src/lib.rs +4 -4
  85. package/crates/team-agent/src/lifecycle/display.rs +7 -3
  86. package/crates/team-agent/src/lifecycle/launch.rs +55 -15
  87. package/crates/team-agent/src/lifecycle/mod.rs +9 -1
  88. package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
  89. package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
  90. package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
  91. package/crates/team-agent/src/lifecycle/restart/common.rs +6 -2
  92. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
  93. package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
  94. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
  95. package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
  96. package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
  97. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
  98. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
  99. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
  100. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
  101. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
  102. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +3 -0
  103. package/crates/team-agent/src/lifecycle/tests.rs +2 -2
  104. package/crates/team-agent/src/main.rs +4 -4
  105. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
  106. package/crates/team-agent/src/mcp_server/mod.rs +11 -2
  107. package/crates/team-agent/src/mcp_server/types.rs +14 -3
  108. package/crates/team-agent/src/mcp_server/wire.rs +230 -68
  109. package/crates/team-agent/src/messaging/delivery.rs +20 -18
  110. package/crates/team-agent/src/messaging/helpers.rs +25 -4
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
  112. package/crates/team-agent/src/messaging/mod.rs +2 -3
  113. package/crates/team-agent/src/messaging/selftest.rs +46 -26
  114. package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
  115. package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
  116. package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
  117. package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
  118. package/crates/team-agent/src/messaging/trust.rs +25 -2
  119. package/crates/team-agent/src/messaging/watchers.rs +37 -9
  120. package/crates/team-agent/src/model/enums.rs +65 -16
  121. package/crates/team-agent/src/model/ids.rs +12 -3
  122. package/crates/team-agent/src/model/paths.rs +28 -7
  123. package/crates/team-agent/src/model/permissions.rs +176 -33
  124. package/crates/team-agent/src/model/routing.rs +66 -20
  125. package/crates/team-agent/src/model/spec.rs +365 -69
  126. package/crates/team-agent/src/model/task_graph.rs +36 -9
  127. package/crates/team-agent/src/model/yaml/tests.rs +24 -6
  128. package/crates/team-agent/src/model/yaml.rs +7 -6
  129. package/crates/team-agent/src/packaging/install.rs +23 -9
  130. package/crates/team-agent/src/packaging/migrate.rs +5 -7
  131. package/crates/team-agent/src/packaging/mod.rs +9 -1
  132. package/crates/team-agent/src/packaging/repair.rs +13 -6
  133. package/crates/team-agent/src/packaging/tests.rs +63 -16
  134. package/crates/team-agent/src/packaging/types.rs +22 -7
  135. package/crates/team-agent/src/platform/argv.rs +4 -1
  136. package/crates/team-agent/src/platform/file_lock.rs +22 -8
  137. package/crates/team-agent/src/platform/process.rs +54 -24
  138. package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
  139. package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
  140. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
  141. package/crates/team-agent/src/provider/classify.rs +127 -42
  142. package/crates/team-agent/src/provider/faults.rs +17 -5
  143. package/crates/team-agent/src/provider/helpers.rs +6 -5
  144. package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
  145. package/crates/team-agent/src/state/persist.rs +28 -0
  146. package/crates/team-agent/src/state/repository.rs +8 -3
  147. package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
  148. package/crates/team-agent/src/tmux_backend.rs +80 -44
  149. package/crates/team-agent/src/topology.rs +40 -20
  150. package/crates/team-agent/src/transport/test_support.rs +20 -23
  151. package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
  152. package/crates/team-agent/src/transport/tests/mod.rs +178 -187
  153. package/crates/team-agent/src/transport/tests/wire.rs +561 -525
  154. package/crates/team-agent/src/transport.rs +12 -17
  155. package/crates/team-agent/src/transport_factory.rs +29 -14
  156. package/package.json +4 -4
@@ -95,32 +95,38 @@ pub fn orphan_gate_json(workspace: &Path, fix: bool, confirm: bool) -> Result<Va
95
95
  let scope = OrphanScanScope::current_workspace(workspace);
96
96
  let report = scan_orphans_bounded(&scope, false);
97
97
  if report.orphans.is_empty() {
98
- return Ok(with_ignored_foreign(json!({
99
- "ok": true,
100
- "gate": "orphans",
101
- "status": "passed",
102
- "scanned": report.scanned,
103
- "dry_run": !fix,
104
- "scanned_at": chrono::Utc::now().to_rfc3339(),
105
- "action_required": false,
106
- "fix": fix,
107
- "orphans": [],
108
- }), &report));
98
+ return Ok(with_ignored_foreign(
99
+ json!({
100
+ "ok": true,
101
+ "gate": "orphans",
102
+ "status": "passed",
103
+ "scanned": report.scanned,
104
+ "dry_run": !fix,
105
+ "scanned_at": chrono::Utc::now().to_rfc3339(),
106
+ "action_required": false,
107
+ "fix": fix,
108
+ "orphans": [],
109
+ }),
110
+ &report,
111
+ ));
109
112
  }
110
113
  if fix {
111
114
  return fix_orphans(&scope, report);
112
115
  }
113
- Ok(with_ignored_foreign(json!({
114
- "ok": false,
115
- "gate": "orphans",
116
- "status": "failed",
117
- "scanned": report.scanned,
118
- "dry_run": true,
119
- "scanned_at": chrono::Utc::now().to_rfc3339(),
120
- "action_required": true,
121
- "fix": false,
122
- "orphans": orphan_values(&report.orphans),
123
- }), &report))
116
+ Ok(with_ignored_foreign(
117
+ json!({
118
+ "ok": false,
119
+ "gate": "orphans",
120
+ "status": "failed",
121
+ "scanned": report.scanned,
122
+ "dry_run": true,
123
+ "scanned_at": chrono::Utc::now().to_rfc3339(),
124
+ "action_required": true,
125
+ "fix": false,
126
+ "orphans": orphan_values(&report.orphans),
127
+ }),
128
+ &report,
129
+ ))
124
130
  }
125
131
 
126
132
  pub fn cleanup_orphans_json(workspace: &Path, confirm: bool) -> Result<Value, CliError> {
@@ -128,26 +134,32 @@ pub fn cleanup_orphans_json(workspace: &Path, confirm: bool) -> Result<Value, Cl
128
134
  let report = scan_orphans_bounded(&scope, false);
129
135
  if confirm {
130
136
  if report.orphans.is_empty() {
131
- return Ok(with_ignored_foreign(json!({
132
- "ok": true,
133
- "scanned": report.scanned,
134
- "orphans": [],
135
- "dry_run": false,
136
- "scanned_at": chrono::Utc::now().to_rfc3339(),
137
- "killed": [],
138
- "failed": [],
139
- }), &report));
137
+ return Ok(with_ignored_foreign(
138
+ json!({
139
+ "ok": true,
140
+ "scanned": report.scanned,
141
+ "orphans": [],
142
+ "dry_run": false,
143
+ "scanned_at": chrono::Utc::now().to_rfc3339(),
144
+ "killed": [],
145
+ "failed": [],
146
+ }),
147
+ &report,
148
+ ));
140
149
  }
141
150
  return cleanup_confirmed(&scope, report);
142
151
  }
143
- Ok(with_ignored_foreign(json!({
144
- "ok": true,
145
- "scanned": report.scanned,
146
- "orphans": orphan_values(&report.orphans),
147
- "dry_run": true,
148
- "scanned_at": chrono::Utc::now().to_rfc3339(),
149
- "action_required": "re-run with --confirm to send SIGTERM",
150
- }), &report))
152
+ Ok(with_ignored_foreign(
153
+ json!({
154
+ "ok": true,
155
+ "scanned": report.scanned,
156
+ "orphans": orphan_values(&report.orphans),
157
+ "dry_run": true,
158
+ "scanned_at": chrono::Utc::now().to_rfc3339(),
159
+ "action_required": "re-run with --confirm to send SIGTERM",
160
+ }),
161
+ &report,
162
+ ))
151
163
  }
152
164
 
153
165
  pub fn has_orphan_residue(workspace: &Path) -> bool {
@@ -188,33 +200,39 @@ pub fn orphan_blocker_detail(workspace: &Path) -> String {
188
200
  fn fix_orphans(scope: &OrphanScanScope, report: ScanReport) -> Result<Value, CliError> {
189
201
  let cleanup = cleanup_report(report, scope);
190
202
  let residual = scan_orphans(scope, false);
191
- Ok(with_ignored_foreign(json!({
192
- "ok": residual.orphans.is_empty() && cleanup.failed.is_empty(),
193
- "gate": "orphans",
194
- "status": if residual.orphans.is_empty() && cleanup.failed.is_empty() { "fixed" } else { "failed" },
195
- "scanned": cleanup.scanned,
196
- "dry_run": false,
197
- "scanned_at": chrono::Utc::now().to_rfc3339(),
198
- "action_required": !residual.orphans.is_empty() || !cleanup.failed.is_empty(),
199
- "fix": true,
200
- "orphans": orphan_values(&residual.orphans),
201
- "killed": cleanup.killed,
202
- "failed": cleanup.failed,
203
- }), &residual))
203
+ Ok(with_ignored_foreign(
204
+ json!({
205
+ "ok": residual.orphans.is_empty() && cleanup.failed.is_empty(),
206
+ "gate": "orphans",
207
+ "status": if residual.orphans.is_empty() && cleanup.failed.is_empty() { "fixed" } else { "failed" },
208
+ "scanned": cleanup.scanned,
209
+ "dry_run": false,
210
+ "scanned_at": chrono::Utc::now().to_rfc3339(),
211
+ "action_required": !residual.orphans.is_empty() || !cleanup.failed.is_empty(),
212
+ "fix": true,
213
+ "orphans": orphan_values(&residual.orphans),
214
+ "killed": cleanup.killed,
215
+ "failed": cleanup.failed,
216
+ }),
217
+ &residual,
218
+ ))
204
219
  }
205
220
 
206
221
  fn cleanup_confirmed(scope: &OrphanScanScope, report: ScanReport) -> Result<Value, CliError> {
207
222
  let cleanup = cleanup_report(report, scope);
208
223
  let residual = scan_orphans(scope, false);
209
- Ok(with_ignored_foreign(json!({
210
- "ok": residual.orphans.is_empty() && cleanup.failed.is_empty(),
211
- "scanned": cleanup.scanned,
212
- "orphans": orphan_values(&residual.orphans),
213
- "dry_run": false,
214
- "scanned_at": chrono::Utc::now().to_rfc3339(),
215
- "killed": cleanup.killed,
216
- "failed": cleanup.failed,
217
- }), &residual))
224
+ Ok(with_ignored_foreign(
225
+ json!({
226
+ "ok": residual.orphans.is_empty() && cleanup.failed.is_empty(),
227
+ "scanned": cleanup.scanned,
228
+ "orphans": orphan_values(&residual.orphans),
229
+ "dry_run": false,
230
+ "scanned_at": chrono::Utc::now().to_rfc3339(),
231
+ "killed": cleanup.killed,
232
+ "failed": cleanup.failed,
233
+ }),
234
+ &residual,
235
+ ))
218
236
  }
219
237
 
220
238
  struct CleanupReport {
@@ -48,14 +48,22 @@ pub enum EventLogError {
48
48
  struct PythonFormatter;
49
49
 
50
50
  impl serde_json::ser::Formatter for PythonFormatter {
51
- fn begin_array_value<W: ?Sized + std::io::Write>(&mut self, w: &mut W, first: bool) -> std::io::Result<()> {
51
+ fn begin_array_value<W: ?Sized + std::io::Write>(
52
+ &mut self,
53
+ w: &mut W,
54
+ first: bool,
55
+ ) -> std::io::Result<()> {
52
56
  if first {
53
57
  Ok(())
54
58
  } else {
55
59
  w.write_all(b", ")
56
60
  }
57
61
  }
58
- fn begin_object_key<W: ?Sized + std::io::Write>(&mut self, w: &mut W, first: bool) -> std::io::Result<()> {
62
+ fn begin_object_key<W: ?Sized + std::io::Write>(
63
+ &mut self,
64
+ w: &mut W,
65
+ first: bool,
66
+ ) -> std::io::Result<()> {
59
67
  if first {
60
68
  Ok(())
61
69
  } else {
@@ -114,7 +122,9 @@ pub struct EventLog {
114
122
  impl EventLog {
115
123
  /// `EventLog(workspace)`:路径 = `<workspace>/.team/logs/events.jsonl`。
116
124
  pub fn new(workspace: &Path) -> Self {
117
- Self { path: logs_dir(workspace).join("events.jsonl") }
125
+ Self {
126
+ path: logs_dir(workspace).join("events.jsonl"),
127
+ }
118
128
  }
119
129
 
120
130
  /// 直接指定 events.jsonl 路径(测试 / 非标准布局)。
@@ -142,7 +152,10 @@ impl EventLog {
142
152
  // 单次 write_all(line+"\n"):POSIX O_APPEND 对 <PIPE_BUF 写原子,避免并发写者交错(对抗 P1)。
143
153
  let mut bytes = to_python_json(&sort_value(&event)).into_bytes();
144
154
  bytes.push(b'\n');
145
- let mut file = std::fs::OpenOptions::new().create(true).append(true).open(&self.path)?;
155
+ let mut file = std::fs::OpenOptions::new()
156
+ .create(true)
157
+ .append(true)
158
+ .open(&self.path)?;
146
159
  file.write_all(&bytes)?;
147
160
  Ok(event)
148
161
  }
@@ -155,7 +168,11 @@ impl EventLog {
155
168
  let text = std::fs::read_to_string(&self.path)?;
156
169
  let lines: Vec<&str> = text.lines().collect();
157
170
  // Python lines[-limit:]:limit==0 → lines[0:] = 全部(负零切片怪癖);limit>len → 全部。
158
- let start = if limit == 0 { 0 } else { lines.len().saturating_sub(limit) };
171
+ let start = if limit == 0 {
172
+ 0
173
+ } else {
174
+ lines.len().saturating_sub(limit)
175
+ };
159
176
  let mut out = Vec::new();
160
177
  for line in &lines[start..] {
161
178
  match serde_json::from_str::<Value>(line) {
@@ -194,7 +211,10 @@ impl EventLog {
194
211
  }
195
212
 
196
213
  fn archive_path(&self, index: u32) -> PathBuf {
197
- let name = self.path.file_name().map_or_else(String::new, |n| n.to_string_lossy().into_owned());
214
+ let name = self
215
+ .path
216
+ .file_name()
217
+ .map_or_else(String::new, |n| n.to_string_lossy().into_owned());
198
218
  self.path.with_file_name(format!("{name}.{index}"))
199
219
  }
200
220
  }
@@ -223,7 +243,10 @@ mod tests {
223
243
  r#"{"event": "u", "msg": "héllo🦀\n世界", "nested": {"a": [1, 2], "b": 2}}"#
224
244
  );
225
245
  // 空 fields。
226
- assert_eq!(to_python_json(&sort_value(&json!({"event":"empty"}))), r#"{"event": "empty"}"#);
246
+ assert_eq!(
247
+ to_python_json(&sort_value(&json!({"event":"empty"}))),
248
+ r#"{"event": "empty"}"#
249
+ );
227
250
  // 类型:bool/null/int 与 Python 一致。
228
251
  assert_eq!(
229
252
  to_python_json(&sort_value(&json!({"missing":false,"x":null,"n":2}))),
@@ -235,7 +258,11 @@ mod tests {
235
258
  fn write_sorts_keys_and_has_ts_event() {
236
259
  let ws = temp_ws();
237
260
  let log = EventLog::new(&ws);
238
- log.write("schema.layout_rebuild", json!({"table":"messages","row_count_before":2,"row_count_after":2,"missing":false})).unwrap();
261
+ log.write(
262
+ "schema.layout_rebuild",
263
+ json!({"table":"messages","row_count_before":2,"row_count_after":2,"missing":false}),
264
+ )
265
+ .unwrap();
239
266
  let line = std::fs::read_to_string(ws.join(".team/logs/events.jsonl")).unwrap();
240
267
  let line = line.trim_end();
241
268
  // 键全排序:event < missing < row_count_after < row_count_before < table < ts。
@@ -243,7 +270,10 @@ mod tests {
243
270
  // ts 是合法 rfc3339 UTC。
244
271
  let v: Value = serde_json::from_str(line).unwrap();
245
272
  let ts = v["ts"].as_str().unwrap();
246
- assert!(chrono::DateTime::parse_from_rfc3339(ts).is_ok(), "ts 非合法 rfc3339: {ts}");
273
+ assert!(
274
+ chrono::DateTime::parse_from_rfc3339(ts).is_ok(),
275
+ "ts 非合法 rfc3339: {ts}"
276
+ );
247
277
  assert!(ts.ends_with("+00:00"));
248
278
  }
249
279
 
@@ -276,7 +306,11 @@ mod tests {
276
306
  // 预置 current >= 5MiB + 已有 .1..=.5 archive(各带标记内容)。
277
307
  std::fs::write(&p, vec![b'x'; EVENT_LOG_ROTATE_BYTES as usize]).unwrap();
278
308
  for i in 1..=EVENT_LOG_ARCHIVE_KEEP {
279
- std::fs::write(p.with_file_name(format!("events.jsonl.{i}")), format!("arc{i}")).unwrap();
309
+ std::fs::write(
310
+ p.with_file_name(format!("events.jsonl.{i}")),
311
+ format!("arc{i}"),
312
+ )
313
+ .unwrap();
280
314
  }
281
315
  // 下一次 write 触发轮转:.5 丢弃,.4→.5 ... .1→.2,current→.1。
282
316
  log.write("after.rotate", json!({})).unwrap();
@@ -285,10 +319,21 @@ mod tests {
285
319
  assert_eq!(new_current.lines().count(), 1);
286
320
  assert!(new_current.contains("after.rotate"));
287
321
  // .1 = 旧 current(5MiB 的 x)。
288
- assert_eq!(std::fs::metadata(p.with_file_name("events.jsonl.1")).unwrap().len(), EVENT_LOG_ROTATE_BYTES);
322
+ assert_eq!(
323
+ std::fs::metadata(p.with_file_name("events.jsonl.1"))
324
+ .unwrap()
325
+ .len(),
326
+ EVENT_LOG_ROTATE_BYTES
327
+ );
289
328
  // .2 = 旧 .1(内容 "arc1");.5 = 旧 .4("arc4");旧 .5("arc5")被丢弃。
290
- assert_eq!(std::fs::read_to_string(p.with_file_name("events.jsonl.2")).unwrap(), "arc1");
291
- assert_eq!(std::fs::read_to_string(p.with_file_name("events.jsonl.5")).unwrap(), "arc4");
329
+ assert_eq!(
330
+ std::fs::read_to_string(p.with_file_name("events.jsonl.2")).unwrap(),
331
+ "arc1"
332
+ );
333
+ assert_eq!(
334
+ std::fs::read_to_string(p.with_file_name("events.jsonl.5")).unwrap(),
335
+ "arc4"
336
+ );
292
337
  }
293
338
 
294
339
  #[test]
@@ -319,7 +364,11 @@ mod tests {
319
364
  for i in 0..3 {
320
365
  log.write("e", json!({ "i": i })).unwrap();
321
366
  }
322
- assert_eq!(log.tail(0).unwrap().len(), 3, "tail(0) == 全部(Python [-0:])");
367
+ assert_eq!(
368
+ log.tail(0).unwrap().len(),
369
+ 3,
370
+ "tail(0) == 全部(Python [-0:])"
371
+ );
323
372
  assert_eq!(log.tail(2).unwrap().len(), 2);
324
373
  assert_eq!(log.tail(99).unwrap().len(), 3);
325
374
  }
@@ -349,7 +398,8 @@ mod tests {
349
398
  assert_eq!(lines.len(), 8 * 50, "无半行/丢行");
350
399
  for line in &lines {
351
400
  // 每行完整合法 JSON 且含 event 键 → 未交错。
352
- let v: Value = serde_json::from_str(line).unwrap_or_else(|e| panic!("交错坏行: {line:?} ({e})"));
401
+ let v: Value =
402
+ serde_json::from_str(line).unwrap_or_else(|e| panic!("交错坏行: {line:?} ({e})"));
353
403
  assert_eq!(v["event"], json!("concurrent"));
354
404
  }
355
405
  }
@@ -367,7 +417,11 @@ mod tests {
367
417
  continue;
368
418
  }
369
419
  let v: Value = serde_json::from_str(line).unwrap();
370
- assert_eq!(to_python_json(&sort_value(&v)), line, "第 {n} 行 round-trip 不字节一致");
420
+ assert_eq!(
421
+ to_python_json(&sort_value(&v)),
422
+ line,
423
+ "第 {n} 行 round-trip 不字节一致"
424
+ );
371
425
  n += 1;
372
426
  }
373
427
  assert_eq!(n, 60, "fixture 应 60 行");
@@ -99,7 +99,11 @@ pub fn next_worker_window(
99
99
  .iter()
100
100
  .any(|w| w.as_str() == window.as_str());
101
101
  let action = if window_exists {
102
- if force { WorkerSpawnAction::ForceReplace } else { WorkerSpawnAction::Noop }
102
+ if force {
103
+ WorkerSpawnAction::ForceReplace
104
+ } else {
105
+ WorkerSpawnAction::Noop
106
+ }
103
107
  } else {
104
108
  WorkerSpawnAction::NewWindow
105
109
  };
@@ -134,7 +138,11 @@ mod worker_placement_tests {
134
138
  return WorkerSpawnTarget::new(session, window, WorkerSpawnAction::NewSession);
135
139
  }
136
140
  let action = if window_exists {
137
- if force { WorkerSpawnAction::ForceReplace } else { WorkerSpawnAction::Noop }
141
+ if force {
142
+ WorkerSpawnAction::ForceReplace
143
+ } else {
144
+ WorkerSpawnAction::Noop
145
+ }
138
146
  } else {
139
147
  WorkerSpawnAction::NewWindow
140
148
  };
@@ -191,8 +199,11 @@ mod tests {
191
199
 
192
200
  #[test]
193
201
  fn exec_provider_mode_returns_exec_current_pane() {
194
- let placement =
195
- leader_placement(LeaderStartMode::ExecProvider, Provider::Claude, Path::new("/tmp/x"));
202
+ let placement = leader_placement(
203
+ LeaderStartMode::ExecProvider,
204
+ Provider::Claude,
205
+ Path::new("/tmp/x"),
206
+ );
196
207
  assert_eq!(placement, LeaderPlacement::ExecCurrentPane);
197
208
  }
198
209
 
@@ -20,15 +20,15 @@
20
20
  //! (Step 8)
21
21
  //! * `worker_env` — worker_spawn_env whitelist + worker_spawn_cwd (Step 3)
22
22
 
23
- pub mod sessions;
24
23
  pub mod manager;
25
- pub mod worker_window_helpers;
26
- pub mod worker_env;
24
+ pub mod overlay;
27
25
  pub mod placement;
28
26
  pub mod recovery;
29
- pub mod overlay;
30
27
  pub mod runtime_sessions;
28
+ pub mod sessions;
31
29
  pub mod tmux_endpoint;
30
+ pub mod worker_env;
31
+ pub mod worker_window_helpers;
32
32
 
33
33
  pub use runtime_sessions::{
34
34
  LeaderLauncherSession, LeaderLauncherSessionError, RuntimeSessionAnomaly, RuntimeSessions,
@@ -22,7 +22,10 @@ pub fn overlay_window_name(session_tag: &str, group_index: usize) -> WindowName
22
22
  if group_index == 0 {
23
23
  WindowName::new(format!("team-agent:{session_tag}:overview"))
24
24
  } else {
25
- WindowName::new(format!("team-agent:{session_tag}:overview-{}", group_index + 1))
25
+ WindowName::new(format!(
26
+ "team-agent:{session_tag}:overview-{}",
27
+ group_index + 1
28
+ ))
26
29
  }
27
30
  }
28
31
 
@@ -113,8 +116,12 @@ mod tests {
113
116
 
114
117
  #[test]
115
118
  fn is_overlay_window_matches_overview_namespace_only() {
116
- assert!(is_overlay_window(&WindowName::new("team-agent:alpha:overview")));
117
- assert!(is_overlay_window(&WindowName::new("team-agent:alpha:overview-2")));
119
+ assert!(is_overlay_window(&WindowName::new(
120
+ "team-agent:alpha:overview"
121
+ )));
122
+ assert!(is_overlay_window(&WindowName::new(
123
+ "team-agent:alpha:overview-2"
124
+ )));
118
125
  assert!(!is_overlay_window(&WindowName::new("developer")));
119
126
  assert!(!is_overlay_window(&WindowName::new("team-w1")));
120
127
  assert!(!is_overlay_window(&WindowName::new("team-alpha:leader")));
@@ -42,6 +42,10 @@ pub struct WorkerSpawnTarget {
42
42
 
43
43
  impl WorkerSpawnTarget {
44
44
  pub fn new(session: SessionName, window: WindowName, action: WorkerSpawnAction) -> Self {
45
- Self { session, window, action }
45
+ Self {
46
+ session,
47
+ window,
48
+ action,
49
+ }
46
50
  }
47
51
  }
@@ -92,9 +92,11 @@ mod tests {
92
92
  match recover_leader_pane(env) {
93
93
  RecoveryOutcome::NeedsUserAttach => {} // expected
94
94
  RecoveryOutcome::AutobindToPane(_) => {
95
- panic!("recovery must not autobind without TMUX_PANE — \
95
+ panic!(
96
+ "recovery must not autobind without TMUX_PANE — \
96
97
  the worker session must NEVER carry a co-located leader \
97
- window in managed mode (E57-3 root)")
98
+ window in managed mode (E57-3 root)"
99
+ )
98
100
  }
99
101
  }
100
102
  }
@@ -205,9 +205,12 @@ impl RuntimeSessions {
205
205
  /// True if a `state.session_name` looked like a leader launcher session
206
206
  /// — the exact 0.3.39 shape unit-3 must refuse before any kill.
207
207
  pub fn worker_session_name_is_leader_prefixed(&self) -> bool {
208
- self.anomalies
209
- .iter()
210
- .any(|a| matches!(a, RuntimeSessionAnomaly::WorkerSessionNameIsLeaderPrefixed { .. }))
208
+ self.anomalies.iter().any(|a| {
209
+ matches!(
210
+ a,
211
+ RuntimeSessionAnomaly::WorkerSessionNameIsLeaderPrefixed { .. }
212
+ )
213
+ })
211
214
  }
212
215
  }
213
216
 
@@ -261,10 +264,7 @@ mod tests {
261
264
  });
262
265
  let r = RuntimeSessions::from_state(&state);
263
266
  assert_eq!(r.worker.unwrap().as_str(), "team-foo");
264
- assert_eq!(
265
- r.leader.unwrap().as_str(),
266
- "team-agent-leader-codex-abc"
267
- );
267
+ assert_eq!(r.leader.unwrap().as_str(), "team-agent-leader-codex-abc");
268
268
  assert!(r.anomalies.is_empty());
269
269
  }
270
270
 
@@ -102,12 +102,13 @@ pub fn assert_topology_invariants(state: &JsonValue, spec: &YamlValue) -> Vec<To
102
102
  ),
103
103
  });
104
104
  }
105
- let agents = state
106
- .get("agents")
107
- .and_then(JsonValue::as_object);
105
+ let agents = state.get("agents").and_then(JsonValue::as_object);
108
106
  if let Some(agents) = agents {
109
107
  for (agent_id, agent) in agents {
110
- let window = agent.get("window").and_then(JsonValue::as_str).unwrap_or("");
108
+ let window = agent
109
+ .get("window")
110
+ .and_then(JsonValue::as_str)
111
+ .unwrap_or("");
111
112
  if window.eq_ignore_ascii_case("leader") {
112
113
  out.push(TopologyViolation {
113
114
  kind: TopologyViolationKind::WorkerWindowNamedLeader,
@@ -120,7 +121,8 @@ pub fn assert_topology_invariants(state: &JsonValue, spec: &YamlValue) -> Vec<To
120
121
  }
121
122
  }
122
123
  // Pane-id collision among agents.
123
- let mut by_pane: std::collections::HashMap<String, Vec<String>> = std::collections::HashMap::new();
124
+ let mut by_pane: std::collections::HashMap<String, Vec<String>> =
125
+ std::collections::HashMap::new();
124
126
  for (agent_id, agent) in agents {
125
127
  if let Some(pane) = agent
126
128
  .get("pane_id")
@@ -229,7 +231,10 @@ mod tests {
229
231
  "leader_receiver": { "pane_id": "%0" }
230
232
  });
231
233
  let v = assert_topology_invariants(&state, &spec_for("alpha"));
232
- assert!(v.is_empty(), "clean state should produce no violations; got {v:?}");
234
+ assert!(
235
+ v.is_empty(),
236
+ "clean state should produce no violations; got {v:?}"
237
+ );
233
238
  }
234
239
 
235
240
  #[test]
@@ -243,7 +248,8 @@ mod tests {
243
248
  });
244
249
  let v = assert_topology_invariants(&state, &spec_for("alpha"));
245
250
  assert!(
246
- v.iter().any(|x| matches!(x.kind, TopologyViolationKind::AgentPaneIdCollision)),
251
+ v.iter()
252
+ .any(|x| matches!(x.kind, TopologyViolationKind::AgentPaneIdCollision)),
247
253
  "must flag AgentPaneIdCollision; got {v:?}"
248
254
  );
249
255
  }
@@ -258,7 +264,8 @@ mod tests {
258
264
  });
259
265
  let v = assert_topology_invariants(&state, &spec_for("alpha"));
260
266
  assert!(
261
- v.iter().any(|x| matches!(x.kind, TopologyViolationKind::LeaderPaneIdCollidesWithAgent)),
267
+ v.iter()
268
+ .any(|x| matches!(x.kind, TopologyViolationKind::LeaderPaneIdCollidesWithAgent)),
262
269
  "must flag LeaderPaneIdCollidesWithAgent; got {v:?}"
263
270
  );
264
271
  }
@@ -272,7 +279,8 @@ mod tests {
272
279
  });
273
280
  let v = assert_topology_invariants(&state, &spec_for("alpha"));
274
281
  assert!(
275
- v.iter().any(|x| matches!(x.kind, TopologyViolationKind::WorkerWindowNamedLeader)),
282
+ v.iter()
283
+ .any(|x| matches!(x.kind, TopologyViolationKind::WorkerWindowNamedLeader)),
276
284
  "must flag WorkerWindowNamedLeader; got {v:?}"
277
285
  );
278
286
  }
@@ -17,8 +17,8 @@
17
17
  //! policy here mirrors it byte-for-byte (verified by `endpoint_priority_matches_backend`
18
18
  //! contract tests).
19
19
 
20
- use std::path::Path;
21
20
  use serde_json::Value;
21
+ use std::path::Path;
22
22
 
23
23
  /// Which state field (or fallback) supplied the chosen endpoint.
24
24
  ///