@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
@@ -18,7 +18,10 @@ fn tick_never_panics_returns_ok_tickreport_on_clean_state() {
18
18
  let report = report.expect("tick must not Err on clean state");
19
19
  assert!(report.ok, "clean tick is ok=true");
20
20
  assert!(!report.stop, "clean tick does not stop the main loop");
21
- assert_eq!(report.reason, None, "clean tick has no degraded/stop reason");
21
+ assert_eq!(
22
+ report.reason, None,
23
+ "clean tick has no degraded/stop reason"
24
+ );
22
25
  assert_eq!(report.persisted, Some(true), "clean tick persisted state");
23
26
  }
24
27
 
@@ -29,15 +32,23 @@ fn tick_save_failure_returns_degraded_not_panic_not_err() {
29
32
  // returned as Ok — NOT a panic, NOT an Err (the main loop must NOT catch+backoff this).
30
33
  let (coord, _calls) = coord_for_test(true, Some(failing_save_hook()), None);
31
34
  let report = coord.tick();
32
- let report = report.expect("save failure is a DEGRADED Ok, NOT an Err (main loop must not backoff)");
35
+ let report =
36
+ report.expect("save failure is a DEGRADED Ok, NOT an Err (main loop must not backoff)");
33
37
  assert!(!report.ok, "bug-084: degraded => ok=false");
34
38
  assert_eq!(
35
39
  report.reason,
36
40
  Some(TickStopReason::PersistenceDegraded),
37
41
  "bug-084: reason=persistence_degraded"
38
42
  );
39
- assert_eq!(report.persisted, Some(false), "bug-084: persisted=Some(false)");
40
- assert!(!report.stop, "bug-084: stop=false (degrade, do NOT exit main loop)");
43
+ assert_eq!(
44
+ report.persisted,
45
+ Some(false),
46
+ "bug-084: persisted=Some(false)"
47
+ );
48
+ assert!(
49
+ !report.stop,
50
+ "bug-084: stop=false (degrade, do NOT exit main loop)"
51
+ );
41
52
  }
42
53
 
43
54
  #[test]
@@ -49,9 +60,14 @@ fn tick_tmux_session_missing_returns_stop_true() {
49
60
  // p2_tick_skips_tmux_gate_when_session_name_absent.)
50
61
  let (coord, _calls) =
51
62
  coord_for_test_with_session(/*session_present=*/ false, "team-missing");
52
- let report = coord.tick().expect("session-missing is a typed report, not an Err");
63
+ let report = coord
64
+ .tick()
65
+ .expect("session-missing is a typed report, not an Err");
53
66
  assert!(!report.ok, "session missing => ok=false");
54
- assert!(report.stop, "session missing => stop=true (break main loop)");
67
+ assert!(
68
+ report.stop,
69
+ "session missing => stop=true (break main loop)"
70
+ );
55
71
  assert_eq!(
56
72
  report.reason,
57
73
  Some(TickStopReason::TmuxSessionMissing),
@@ -68,8 +84,7 @@ fn tick_side_effect_order_is_the_fixed_sequence() {
68
84
  // detect_deadlocks -> detect_compaction -> detect_drift -> detect_api_errors ->
69
85
  // ATOMIC_save (bug-084 wrap) -> collect_results -> prune_dedupe_log.
70
86
  // The porter must push each step name into the injected recorder at its call site.
71
- let recorder: OrderRecorder =
72
- std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
87
+ let recorder: OrderRecorder = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
73
88
  let (coord, _calls) = coord_for_test(true, None, Some(std::sync::Arc::clone(&recorder)));
74
89
  let _ = coord.tick().expect("tick ok");
75
90
  let order = recorder.lock().unwrap().clone();
@@ -101,11 +116,17 @@ fn tick_side_effect_order_is_the_fixed_sequence() {
101
116
  "collect_results",
102
117
  "prune_dedupe_log",
103
118
  ];
104
- assert_eq!(order, expected, "tick side-effect ORDER must match the fixed sequence");
119
+ assert_eq!(
120
+ order, expected,
121
+ "tick side-effect ORDER must match the fixed sequence"
122
+ );
105
123
  // ATOMIC save is the LAST mutation before read-only collect/prune (bug-084 wrap point).
106
124
  let save_idx = order.iter().position(|s| *s == "atomic_save").unwrap();
107
125
  let collect_idx = order.iter().position(|s| *s == "collect_results").unwrap();
108
- assert!(save_idx < collect_idx, "save precedes collect (bug-084 wrap is the last mutation)");
126
+ assert!(
127
+ save_idx < collect_idx,
128
+ "save precedes collect (bug-084 wrap is the last mutation)"
129
+ );
109
130
  }
110
131
 
111
132
  #[test]
@@ -132,7 +153,11 @@ fn health_ok_is_conjunction_of_running_metadata_ok_and_schema_ok() {
132
153
  let (coord, _calls) = coord_for_test(true, None, None);
133
154
  let h = coord.health().expect("health is a typed report");
134
155
  assert!(!h.ok, "no pid file => not running => ok=false");
135
- assert_eq!(h.status, CoordinatorHealthStatus::Missing, "no pid => status=missing");
156
+ assert_eq!(
157
+ h.status,
158
+ CoordinatorHealthStatus::Missing,
159
+ "no pid => status=missing"
160
+ );
136
161
  assert!(!h.metadata_ok, "no metadata => metadata_ok=false");
137
162
  }
138
163
 
@@ -193,7 +218,10 @@ fn read_coordinator_metadata_missing_file_is_none() {
193
218
  fn p2_tick_skips_tmux_gate_when_session_name_absent() {
194
219
  let (coord, _calls) = coord_for_test(false, None, None);
195
220
  let report = coord.tick().unwrap();
196
- assert!(!report.stop, "missing/null session_name must skip the gate, not stop the daemon");
221
+ assert!(
222
+ !report.stop,
223
+ "missing/null session_name must skip the gate, not stop the daemon"
224
+ );
197
225
  assert_ne!(report.reason, Some(TickStopReason::TmuxSessionMissing));
198
226
  }
199
227
 
@@ -223,8 +251,14 @@ fn p2_render_result_received_truncates_summary_to_80_chars() {
223
251
  let long = "x".repeat(200);
224
252
  let e = serde_json::json!({"event":"result_received","agent_id":"w1","summary": long});
225
253
  let line = render_event_line(&e).expect("renders");
226
- assert!(line.contains(&"x".repeat(80)), "first 80 summary chars are kept");
227
- assert!(!line.contains(&"x".repeat(81)), "summary must be truncated to 80 chars");
254
+ assert!(
255
+ line.contains(&"x".repeat(80)),
256
+ "first 80 summary chars are kept"
257
+ );
258
+ assert!(
259
+ !line.contains(&"x".repeat(81)),
260
+ "summary must be truncated to 80 chars"
261
+ );
228
262
  }
229
263
 
230
264
  // P1 — idle_takeover.unknown_persistent must carry the auth_mode field (null when absent)
@@ -252,7 +286,8 @@ fn p2_unknown_persistent_event_serializes_auth_mode_key() {
252
286
  #[test]
253
287
  fn p2_abnormal_matches_lowercased_signature_too() {
254
288
  let reg = MockRegistry::new(&[], &["timeout"]);
255
- let records = vec![serde_json::json!({"raw":"nothing here","signature":"TimeoutError","kind":"error"})];
289
+ let records =
290
+ vec![serde_json::json!({"raw":"nothing here","signature":"TimeoutError","kind":"error"})];
256
291
  let out = process_abnormal_records(
257
292
  &records,
258
293
  &reg,
@@ -9,38 +9,64 @@ use super::*;
9
9
  fn render_result_received_collapses_whitespace_and_defaults() {
10
10
  // watch.py:48-49 + _result_line — "result_received: <agent|-> -> <clean summary>".
11
11
  let e = serde_json::json!({"event": "result_received", "agent_id": "w1", "summary": "did the\nthing"});
12
- assert_eq!(render_event_line(&e), Some("result_received: w1 -> did the thing".to_string()));
12
+ assert_eq!(
13
+ render_event_line(&e),
14
+ Some("result_received: w1 -> did the thing".to_string())
15
+ );
13
16
  // missing agent + summary → both '-'.
14
17
  let bare = serde_json::json!({"event": "result_received"});
15
- assert_eq!(render_event_line(&bare), Some("result_received: - -> -".to_string()));
18
+ assert_eq!(
19
+ render_event_line(&bare),
20
+ Some("result_received: - -> -".to_string())
21
+ );
16
22
  }
17
23
 
18
24
  #[test]
19
25
  fn render_injected_and_submitted_share_prefix_and_truncate_message_id() {
20
26
  // watch.py:50-51 — both render as "leader_receiver.injected"; message_id truncated to 12.
21
27
  let inj = serde_json::json!({"event": "leader_receiver.injected", "message_id": "abcdef0123456789", "recipient": "w2"});
22
- assert_eq!(render_event_line(&inj), Some("leader_receiver.injected: abcdef012345 -> w2".to_string()));
28
+ assert_eq!(
29
+ render_event_line(&inj),
30
+ Some("leader_receiver.injected: abcdef012345 -> w2".to_string())
31
+ );
23
32
  // submitted uses same label, msg_id fallback, `to` recipient fallback.
24
33
  let sub = serde_json::json!({"event": "leader_receiver.submitted", "msg_id": "xy", "to": "w3"});
25
- assert_eq!(render_event_line(&sub), Some("leader_receiver.injected: xy -> w3".to_string()));
34
+ assert_eq!(
35
+ render_event_line(&sub),
36
+ Some("leader_receiver.injected: xy -> w3".to_string())
37
+ );
26
38
  }
27
39
 
28
40
  #[test]
29
41
  fn render_send_failed_uses_reason_then_error_fallback() {
30
42
  // watch.py:52-53 — reason || error || '-', recipient || to || target || '-', whitespace-cleaned.
31
- let with_reason = serde_json::json!({"event": "send.failed", "recipient": "w4", "reason": " pane gone "});
32
- assert_eq!(render_event_line(&with_reason), Some("send.failed: w4 reason=pane gone".to_string()));
43
+ let with_reason =
44
+ serde_json::json!({"event": "send.failed", "recipient": "w4", "reason": " pane gone "});
45
+ assert_eq!(
46
+ render_event_line(&with_reason),
47
+ Some("send.failed: w4 reason=pane gone".to_string())
48
+ );
33
49
  let with_error = serde_json::json!({"event": "send.failed", "target": "w5", "error": "boom"});
34
- assert_eq!(render_event_line(&with_error), Some("send.failed: w5 reason=boom".to_string()));
50
+ assert_eq!(
51
+ render_event_line(&with_error),
52
+ Some("send.failed: w5 reason=boom".to_string())
53
+ );
35
54
  }
36
55
 
37
56
  #[test]
38
57
  fn render_rebind_required_uses_pane_and_reason_fallbacks() {
39
58
  // watch.py:54-57 — old_pane_id || pane_id || target || '-'; reason || rediscovery_status || '-'.
40
59
  let e = serde_json::json!({"event": "leader_receiver.rebind_required", "old_pane_id": "%9", "reason": "lost"});
41
- assert_eq!(render_event_line(&e), Some("leader_receiver.rebind_required: pane=%9 reason=lost".to_string()));
42
- let no_reason = serde_json::json!({"event": "leader_receiver.rebind_required", "pane_id": "%7"});
43
- assert_eq!(render_event_line(&no_reason), Some("leader_receiver.rebind_required: pane=%7 reason=-".to_string()));
60
+ assert_eq!(
61
+ render_event_line(&e),
62
+ Some("leader_receiver.rebind_required: pane=%9 reason=lost".to_string())
63
+ );
64
+ let no_reason =
65
+ serde_json::json!({"event": "leader_receiver.rebind_required", "pane_id": "%7"});
66
+ assert_eq!(
67
+ render_event_line(&no_reason),
68
+ Some("leader_receiver.rebind_required: pane=%7 reason=-".to_string())
69
+ );
44
70
  }
45
71
 
46
72
  #[test]
@@ -49,16 +75,32 @@ fn render_api_error_defaults_unknown_class_and_dash() {
49
75
  let error_class = ["Over", "loaded"].concat();
50
76
  let code = (500 + 29).to_string();
51
77
  let e = serde_json::json!({"event": "leader.api_error", "error_class": error_class, "provider": "claude_code", "matched_pattern_snippet": format!("{code} too many")});
52
- assert_eq!(render_event_line(&e), Some(format!("leader.api_error: {} provider=claude_code snippet={} too many", ["Over", "loaded"].concat(), code)));
78
+ assert_eq!(
79
+ render_event_line(&e),
80
+ Some(format!(
81
+ "leader.api_error: {} provider=claude_code snippet={} too many",
82
+ ["Over", "loaded"].concat(),
83
+ code
84
+ ))
85
+ );
53
86
  let bare = serde_json::json!({"event": "leader.api_error"});
54
- assert_eq!(render_event_line(&bare), Some("leader.api_error: Unknown provider=- snippet=-".to_string()));
87
+ assert_eq!(
88
+ render_event_line(&bare),
89
+ Some("leader.api_error: Unknown provider=- snippet=-".to_string())
90
+ );
55
91
  }
56
92
 
57
93
  #[test]
58
94
  fn render_non_renderable_events_return_none() {
59
95
  // watch.py:63 — coordinator.* / unknown events → None.
60
- assert_eq!(render_event_line(&serde_json::json!({"event": "coordinator.boot"})), None);
61
- assert_eq!(render_event_line(&serde_json::json!({"event": "unknown.thing"})), None);
96
+ assert_eq!(
97
+ render_event_line(&serde_json::json!({"event": "coordinator.boot"})),
98
+ None
99
+ );
100
+ assert_eq!(
101
+ render_event_line(&serde_json::json!({"event": "unknown.thing"})),
102
+ None
103
+ );
62
104
  }
63
105
 
64
106
  // ═════════════════════════════════════════════════════════════════════════
@@ -88,7 +130,8 @@ fn seed_watch_workspace(event_summary: &str, archive_bytes: Option<&[u8]>) -> Wo
88
130
  ));
89
131
  let logs = crate::model::paths::logs_dir(&dir);
90
132
  std::fs::create_dir_all(&logs).unwrap();
91
- let line = serde_json::json!({"event": "result_received", "agent_id": "w1", "summary": event_summary});
133
+ let line =
134
+ serde_json::json!({"event": "result_received", "agent_id": "w1", "summary": event_summary});
92
135
  std::fs::write(
93
136
  logs.join("events.jsonl"),
94
137
  format!("{}\n", serde_json::to_string(&line).unwrap()),
@@ -125,14 +168,20 @@ fn watch_rotation_emits_marker_once_resets_offset_and_does_not_replay() {
125
168
  "first segment line is rendered exactly once"
126
169
  );
127
170
  let offset_after_first = cursor.event_offset;
128
- assert!(offset_after_first > 0, "offset advanced past the consumed segment");
171
+ assert!(
172
+ offset_after_first > 0,
173
+ "offset advanced past the consumed segment"
174
+ );
129
175
 
130
176
  // Simulate rotation: an archive segment now appears (archive_signature changes from
131
177
  // None -> Some), and the live events.jsonl is replaced with a fresh, SHORTER file
132
178
  // whose new content must NOT include the archived "first segment".
133
179
  let logs = crate::model::paths::logs_dir(ws.as_path());
134
- std::fs::write(logs.join("events.jsonl.1"), b"archived old bytes that must never replay\n")
135
- .unwrap();
180
+ std::fs::write(
181
+ logs.join("events.jsonl.1"),
182
+ b"archived old bytes that must never replay\n",
183
+ )
184
+ .unwrap();
136
185
  let fresh = serde_json::json!({"event": "result_received", "agent_id": "w1", "summary": "post rotation"});
137
186
  std::fs::write(
138
187
  logs.join("events.jsonl"),
@@ -142,13 +191,18 @@ fn watch_rotation_emits_marker_once_resets_offset_and_does_not_replay() {
142
191
 
143
192
  let second = collect_watch_lines(&ws, &mut cursor, &store, None).unwrap();
144
193
  let marker_count = second.iter().filter(|l| **l == ROTATION_MARKER).count();
145
- assert_eq!(marker_count, 1, "ROTATION_MARKER emitted exactly once on rotation");
194
+ assert_eq!(
195
+ marker_count, 1,
196
+ "ROTATION_MARKER emitted exactly once on rotation"
197
+ );
146
198
  assert!(
147
199
  !second.iter().any(|l| l.contains("first segment")),
148
200
  "archived segment is NOT replayed"
149
201
  );
150
202
  assert!(
151
- !second.iter().any(|l| l.contains("old bytes that must never replay")),
203
+ !second
204
+ .iter()
205
+ .any(|l| l.contains("old bytes that must never replay")),
152
206
  "archive file contents are NEVER read/replayed"
153
207
  );
154
208
  assert!(
@@ -230,7 +230,12 @@ impl MessageStore {
230
230
 
231
231
  /// `mark` (`core.py:116-138`) — the messages.status state machine (this slice
232
232
  /// excludes the `delivery_tokens` side-effect, which is deferred).
233
- pub fn mark(&self, message_id: &str, status: &str, error: Option<&str>) -> Result<(), MessageStoreError> {
233
+ pub fn mark(
234
+ &self,
235
+ message_id: &str,
236
+ status: &str,
237
+ error: Option<&str>,
238
+ ) -> Result<(), MessageStoreError> {
234
239
  let conn = crate::db::schema::open_db(&self.path)?;
235
240
  let now = now_ts();
236
241
  conn.execute(
@@ -594,7 +599,15 @@ mod tests {
594
599
  fn create_message_inserts_accepted_row() {
595
600
  let s = store();
596
601
  let mid = s
597
- .create_message(Some("task_1"), "leader", "alice", "hello", None, true, Some("team_A"))
602
+ .create_message(
603
+ Some("task_1"),
604
+ "leader",
605
+ "alice",
606
+ "hello",
607
+ None,
608
+ true,
609
+ Some("team_A"),
610
+ )
598
611
  .unwrap();
599
612
  assert!(mid.starts_with("msg_"), "id: {mid}");
600
613
  assert_eq!(mid.len(), 16, "msg_ + 12 hex");
@@ -604,7 +617,10 @@ mod tests {
604
617
  assert_eq!(col_str(&c, &mid, "task_id").as_deref(), Some("task_1"));
605
618
  assert_eq!(col_str(&c, &mid, "sender").as_deref(), Some("leader"));
606
619
  assert_eq!(col_str(&c, &mid, "recipient").as_deref(), Some("alice"));
607
- assert_eq!(col_str(&c, &mid, "owner_team_id").as_deref(), Some("team_A"));
620
+ assert_eq!(
621
+ col_str(&c, &mid, "owner_team_id").as_deref(),
622
+ Some("team_A")
623
+ );
608
624
  assert_eq!(col_str(&c, &mid, "content").as_deref(), Some("hello"));
609
625
  assert_eq!(col_str(&c, &mid, "artifact_refs").as_deref(), Some("[]"));
610
626
  assert_eq!(col_str(&c, &mid, "reply_to"), None);
@@ -618,7 +634,9 @@ mod tests {
618
634
  #[test]
619
635
  fn create_message_no_task_no_owner_ack_false() {
620
636
  let s = store();
621
- let mid = s.create_message(None, "leader", "bob", "hi", None, false, None).unwrap();
637
+ let mid = s
638
+ .create_message(None, "leader", "bob", "hi", None, false, None)
639
+ .unwrap();
622
640
  let c = read(&s);
623
641
  assert_eq!(col_str(&c, &mid, "task_id"), None);
624
642
  assert_eq!(col_str(&c, &mid, "owner_team_id"), None);
@@ -631,9 +649,14 @@ mod tests {
631
649
  #[test]
632
650
  fn claim_for_delivery_first_caller_wins() {
633
651
  let s = store();
634
- let mid = s.create_message(Some("t"), "s", "r", "c", None, true, None).unwrap();
652
+ let mid = s
653
+ .create_message(Some("t"), "s", "r", "c", None, true, None)
654
+ .unwrap();
635
655
 
636
- assert!(s.claim_for_delivery(&mid).unwrap(), "accepted is eligible → claim wins");
656
+ assert!(
657
+ s.claim_for_delivery(&mid).unwrap(),
658
+ "accepted is eligible → claim wins"
659
+ );
637
660
  let c = read(&s);
638
661
  assert_eq!(status_of(&c, &mid), "target_resolved");
639
662
  assert_eq!(col_i64(&c, &mid, "delivery_attempts"), 1);
@@ -644,9 +667,14 @@ mod tests {
644
667
  // Atomic single-winner: once target_resolved, a re-claim returns false and
645
668
  // must NOT bump delivery_attempts again.
646
669
  let s = store();
647
- let mid = s.create_message(Some("t"), "s", "r", "c", None, true, None).unwrap();
670
+ let mid = s
671
+ .create_message(Some("t"), "s", "r", "c", None, true, None)
672
+ .unwrap();
648
673
  assert!(s.claim_for_delivery(&mid).unwrap());
649
- assert!(!s.claim_for_delivery(&mid).unwrap(), "already target_resolved → no second winner");
674
+ assert!(
675
+ !s.claim_for_delivery(&mid).unwrap(),
676
+ "already target_resolved → no second winner"
677
+ );
650
678
 
651
679
  let c = read(&s);
652
680
  assert_eq!(status_of(&c, &mid), "target_resolved");
@@ -663,7 +691,9 @@ mod tests {
663
691
  fn claim_for_delivery_ineligible_status_is_false() {
664
692
  // 'failed' is not in the eligible set → claim returns false, status unchanged.
665
693
  let s = store();
666
- let mid = s.create_message(Some("t"), "s", "r", "c", None, true, None).unwrap();
694
+ let mid = s
695
+ .create_message(Some("t"), "s", "r", "c", None, true, None)
696
+ .unwrap();
667
697
  s.mark(&mid, "failed", Some("boom")).unwrap();
668
698
  assert!(!s.claim_for_delivery(&mid).unwrap());
669
699
  assert_eq!(status_of(&read(&s), &mid), "failed");
@@ -674,11 +704,16 @@ mod tests {
674
704
  #[test]
675
705
  fn mark_injected_sets_status_and_delivered_at() {
676
706
  let s = store();
677
- let mid = s.create_message(Some("t"), "s", "r", "c", None, true, None).unwrap();
707
+ let mid = s
708
+ .create_message(Some("t"), "s", "r", "c", None, true, None)
709
+ .unwrap();
678
710
  s.mark(&mid, "injected", None).unwrap();
679
711
  let c = read(&s);
680
712
  assert_eq!(status_of(&c, &mid), "injected");
681
- assert!(col_str(&c, &mid, "delivered_at").is_some(), "delivered_at set for injected");
713
+ assert!(
714
+ col_str(&c, &mid, "delivered_at").is_some(),
715
+ "delivered_at set for injected"
716
+ );
682
717
  assert_eq!(col_str(&c, &mid, "acknowledged_at"), None);
683
718
  }
684
719
 
@@ -686,22 +721,34 @@ mod tests {
686
721
  fn mark_acknowledged_is_sticky_against_delivery_statuses() {
687
722
  // CASE guard: once acknowledged, marks to injected/visible/... are ignored.
688
723
  let s = store();
689
- let mid = s.create_message(Some("t"), "s", "r", "c", None, true, None).unwrap();
724
+ let mid = s
725
+ .create_message(Some("t"), "s", "r", "c", None, true, None)
726
+ .unwrap();
690
727
  s.mark(&mid, "acknowledged", None).unwrap();
691
728
  assert_eq!(status_of(&read(&s), &mid), "acknowledged");
692
729
  assert!(col_str(&read(&s), &mid, "acknowledged_at").is_some());
693
730
 
694
731
  s.mark(&mid, "injected", None).unwrap();
695
- assert_eq!(status_of(&read(&s), &mid), "acknowledged", "injected ignored after ack");
732
+ assert_eq!(
733
+ status_of(&read(&s), &mid),
734
+ "acknowledged",
735
+ "injected ignored after ack"
736
+ );
696
737
  s.mark(&mid, "visible", None).unwrap();
697
- assert_eq!(status_of(&read(&s), &mid), "acknowledged", "visible ignored after ack");
738
+ assert_eq!(
739
+ status_of(&read(&s), &mid),
740
+ "acknowledged",
741
+ "visible ignored after ack"
742
+ );
698
743
  }
699
744
 
700
745
  #[test]
701
746
  fn mark_acknowledged_then_failed_overwrites() {
702
747
  // 'failed' is NOT in the guarded delivery set → it overwrites acknowledged.
703
748
  let s = store();
704
- let mid = s.create_message(Some("t"), "s", "r", "c", None, true, None).unwrap();
749
+ let mid = s
750
+ .create_message(Some("t"), "s", "r", "c", None, true, None)
751
+ .unwrap();
705
752
  s.mark(&mid, "acknowledged", None).unwrap();
706
753
  s.mark(&mid, "failed", Some("x")).unwrap();
707
754
  assert_eq!(status_of(&read(&s), &mid), "failed");
@@ -710,7 +757,9 @@ mod tests {
710
757
  #[test]
711
758
  fn mark_overwrites_for_non_acknowledged() {
712
759
  let s = store();
713
- let mid = s.create_message(Some("t"), "s", "r", "c", None, true, None).unwrap();
760
+ let mid = s
761
+ .create_message(Some("t"), "s", "r", "c", None, true, None)
762
+ .unwrap();
714
763
  s.mark(&mid, "injected", None).unwrap();
715
764
  s.mark(&mid, "visible", None).unwrap();
716
765
  assert_eq!(status_of(&read(&s), &mid), "visible");
@@ -742,16 +791,31 @@ mod tests {
742
791
  // and proposed id → second is deduped to the first winner.
743
792
  let s = store();
744
793
  let r1 = s
745
- .claim_leader_notification_delivery(params("res_1", Some("team_A"), Some(7), Some("uuid-AAA"), "msg_notif_1"))
794
+ .claim_leader_notification_delivery(params(
795
+ "res_1",
796
+ Some("team_A"),
797
+ Some(7),
798
+ Some("uuid-AAA"),
799
+ "msg_notif_1",
800
+ ))
746
801
  .unwrap();
747
802
  assert_eq!(r1.status, "claimed_by_you");
748
803
  assert_eq!(r1.notified_message_id, "msg_notif_1");
749
804
 
750
805
  let r2 = s
751
- .claim_leader_notification_delivery(params("res_1", Some("team_A"), Some(7), Some("uuid-BBB"), "msg_notif_2"))
806
+ .claim_leader_notification_delivery(params(
807
+ "res_1",
808
+ Some("team_A"),
809
+ Some(7),
810
+ Some("uuid-BBB"),
811
+ "msg_notif_2",
812
+ ))
752
813
  .unwrap();
753
814
  assert_eq!(r2.status, "already_notified_by");
754
- assert_eq!(r2.notified_message_id, "msg_notif_1", "loser sees first winner's id, not its own");
815
+ assert_eq!(
816
+ r2.notified_message_id, "msg_notif_1",
817
+ "loser sees first winner's id, not its own"
818
+ );
755
819
 
756
820
  // Exactly ONE row — carrying the FIRST caller's uuid (uuid not in the key).
757
821
  assert_eq!(
@@ -769,12 +833,27 @@ mod tests {
769
833
  #[test]
770
834
  fn leader_notification_different_epoch_is_a_new_claim() {
771
835
  let s = store();
772
- s.claim_leader_notification_delivery(params("res_1", Some("team_A"), Some(7), Some("uuid-AAA"), "msg_notif_1"))
773
- .unwrap();
836
+ s.claim_leader_notification_delivery(params(
837
+ "res_1",
838
+ Some("team_A"),
839
+ Some(7),
840
+ Some("uuid-AAA"),
841
+ "msg_notif_1",
842
+ ))
843
+ .unwrap();
774
844
  let r3 = s
775
- .claim_leader_notification_delivery(params("res_1", Some("team_A"), Some(8), Some("uuid-AAA"), "msg_notif_3"))
845
+ .claim_leader_notification_delivery(params(
846
+ "res_1",
847
+ Some("team_A"),
848
+ Some(8),
849
+ Some("uuid-AAA"),
850
+ "msg_notif_3",
851
+ ))
776
852
  .unwrap();
777
- assert_eq!(r3.status, "claimed_by_you", "different owner_epoch → different PK → new claim");
853
+ assert_eq!(
854
+ r3.status, "claimed_by_you",
855
+ "different owner_epoch → different PK → new claim"
856
+ );
778
857
 
779
858
  let rows = notif_rows(&read(&s));
780
859
  assert_eq!(rows.len(), 2);
@@ -789,20 +868,41 @@ mod tests {
789
868
  // yields a different derived epoch → a new claim.
790
869
  let s = store();
791
870
  let a1 = s
792
- .claim_leader_notification_delivery(params("res_2", Some("team_B"), None, Some("uuid-AAA"), "m1"))
871
+ .claim_leader_notification_delivery(params(
872
+ "res_2",
873
+ Some("team_B"),
874
+ None,
875
+ Some("uuid-AAA"),
876
+ "m1",
877
+ ))
793
878
  .unwrap();
794
879
  assert_eq!(a1.status, "claimed_by_you");
795
880
 
796
881
  let a2 = s
797
- .claim_leader_notification_delivery(params("res_2", Some("team_B"), None, Some("uuid-AAA"), "m2"))
882
+ .claim_leader_notification_delivery(params(
883
+ "res_2",
884
+ Some("team_B"),
885
+ None,
886
+ Some("uuid-AAA"),
887
+ "m2",
888
+ ))
798
889
  .unwrap();
799
890
  assert_eq!(a2.status, "already_notified_by");
800
891
  assert_eq!(a2.notified_message_id, "m1");
801
892
 
802
893
  let a3 = s
803
- .claim_leader_notification_delivery(params("res_2", Some("team_B"), None, Some("uuid-BBB"), "m3"))
894
+ .claim_leader_notification_delivery(params(
895
+ "res_2",
896
+ Some("team_B"),
897
+ None,
898
+ Some("uuid-BBB"),
899
+ "m3",
900
+ ))
804
901
  .unwrap();
805
- assert_eq!(a3.status, "claimed_by_you", "different uuid → different derived epoch → new claim");
902
+ assert_eq!(
903
+ a3.status, "claimed_by_you",
904
+ "different uuid → different derived epoch → new claim"
905
+ );
806
906
 
807
907
  // Stored epochs equal the crc32 derivation of each uuid.
808
908
  let rows = notif_rows(&read(&s));
@@ -829,7 +929,9 @@ mod tests {
829
929
  let mut rows = c
830
930
  .prepare("select a, b from peer_allowlist order by a, b")
831
931
  .unwrap()
832
- .query_map([], |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)))
932
+ .query_map([], |row| {
933
+ Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
934
+ })
833
935
  .unwrap()
834
936
  .collect::<Result<Vec<_>, _>>()
835
937
  .unwrap();
@@ -851,7 +953,9 @@ mod tests {
851
953
  #[test]
852
954
  fn fix_b1_mark_preserves_existing_error_when_none_given() {
853
955
  let s = store();
854
- let mid = s.create_message(Some("t"), "a", "b", "c", None, true, None).unwrap();
956
+ let mid = s
957
+ .create_message(Some("t"), "a", "b", "c", None, true, None)
958
+ .unwrap();
855
959
 
856
960
  s.mark(&mid, "failed", Some("boom")).unwrap();
857
961
  assert_eq!(col_str(&read(&s), &mid, "error").as_deref(), Some("boom"));
@@ -861,7 +965,11 @@ mod tests {
861
965
  s.mark(&mid, "injected", None).unwrap();
862
966
  let c = read(&s);
863
967
  assert_eq!(status_of(&c, &mid), "injected");
864
- assert_eq!(col_str(&c, &mid, "error").as_deref(), Some("boom"), "existing error must survive a no-error mark");
968
+ assert_eq!(
969
+ col_str(&c, &mid, "error").as_deref(),
970
+ Some("boom"),
971
+ "existing error must survive a no-error mark"
972
+ );
865
973
 
866
974
  // A NEW error overwrites.
867
975
  s.mark(&mid, "failed", Some("second")).unwrap();