@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
@@ -30,30 +30,102 @@ fn rotation_marker_is_byte_exact() {
30
30
  #[test]
31
31
  fn status_enums_serialize_to_exact_python_strings() {
32
32
  let cases: &[(String, &str)] = &[
33
- (serde_json::to_string(&CoordinatorHealthStatus::Missing).unwrap(), "\"missing\""),
34
- (serde_json::to_string(&CoordinatorHealthStatus::InvalidPid).unwrap(), "\"invalid_pid\""),
35
- (serde_json::to_string(&CoordinatorHealthStatus::Running).unwrap(), "\"running\""),
36
- (serde_json::to_string(&CoordinatorHealthStatus::Stale).unwrap(), "\"stale\""),
37
- (serde_json::to_string(&StartOutcome::AlreadyRunning).unwrap(), "\"already_running\""),
38
- (serde_json::to_string(&StartOutcome::RestartIncompatibleStopFailed).unwrap(), "\"restart_incompatible_stop_failed\""),
39
- (serde_json::to_string(&StartOutcome::SchemaIncompatible).unwrap(), "\"schema_incompatible\""),
40
- (serde_json::to_string(&StartOutcome::Started).unwrap(), "\"started\""),
41
- (serde_json::to_string(&StartOutcome::StartedAfterRotation).unwrap(), "\"started_after_rotation\""),
42
- (serde_json::to_string(&StopOutcome::Missing).unwrap(), "\"missing\""),
43
- (serde_json::to_string(&StopOutcome::InvalidPidRemoved).unwrap(), "\"invalid_pid_removed\""),
44
- (serde_json::to_string(&StopOutcome::KillFailed).unwrap(), "\"kill_failed\""),
45
- (serde_json::to_string(&StopOutcome::Stopped).unwrap(), "\"stopped\""),
46
- (serde_json::to_string(&TickStopReason::TmuxSessionMissing).unwrap(), "\"tmux_session_missing\""),
47
- (serde_json::to_string(&TickStopReason::PersistenceDegraded).unwrap(), "\"persistence_degraded\""),
48
- (serde_json::to_string(&MetadataSource::Boot).unwrap(), "\"boot\""),
49
- (serde_json::to_string(&MetadataSource::Start).unwrap(), "\"start\""),
50
- (serde_json::to_string(&AbnormalDecision::Skip).unwrap(), "\"skip\""),
51
- (serde_json::to_string(&AbnormalDecision::NotifyBlacklist).unwrap(), "\"notify_blacklist\""),
52
- (serde_json::to_string(&AbnormalDecision::NotifyDefault).unwrap(), "\"notify_default\""),
53
- (serde_json::to_string(&WholeTeamGoneClass::Alive).unwrap(), "\"alive\""),
54
- (serde_json::to_string(&WholeTeamGoneClass::CleanShutdown).unwrap(), "\"clean_shutdown\""),
55
- (serde_json::to_string(&WholeTeamGoneClass::RestartInProgress).unwrap(), "\"restart_in_progress\""),
56
- (serde_json::to_string(&WholeTeamGoneClass::UnexpectedExit).unwrap(), "\"unexpected_exit\""),
33
+ (
34
+ serde_json::to_string(&CoordinatorHealthStatus::Missing).unwrap(),
35
+ "\"missing\"",
36
+ ),
37
+ (
38
+ serde_json::to_string(&CoordinatorHealthStatus::InvalidPid).unwrap(),
39
+ "\"invalid_pid\"",
40
+ ),
41
+ (
42
+ serde_json::to_string(&CoordinatorHealthStatus::Running).unwrap(),
43
+ "\"running\"",
44
+ ),
45
+ (
46
+ serde_json::to_string(&CoordinatorHealthStatus::Stale).unwrap(),
47
+ "\"stale\"",
48
+ ),
49
+ (
50
+ serde_json::to_string(&StartOutcome::AlreadyRunning).unwrap(),
51
+ "\"already_running\"",
52
+ ),
53
+ (
54
+ serde_json::to_string(&StartOutcome::RestartIncompatibleStopFailed).unwrap(),
55
+ "\"restart_incompatible_stop_failed\"",
56
+ ),
57
+ (
58
+ serde_json::to_string(&StartOutcome::SchemaIncompatible).unwrap(),
59
+ "\"schema_incompatible\"",
60
+ ),
61
+ (
62
+ serde_json::to_string(&StartOutcome::Started).unwrap(),
63
+ "\"started\"",
64
+ ),
65
+ (
66
+ serde_json::to_string(&StartOutcome::StartedAfterRotation).unwrap(),
67
+ "\"started_after_rotation\"",
68
+ ),
69
+ (
70
+ serde_json::to_string(&StopOutcome::Missing).unwrap(),
71
+ "\"missing\"",
72
+ ),
73
+ (
74
+ serde_json::to_string(&StopOutcome::InvalidPidRemoved).unwrap(),
75
+ "\"invalid_pid_removed\"",
76
+ ),
77
+ (
78
+ serde_json::to_string(&StopOutcome::KillFailed).unwrap(),
79
+ "\"kill_failed\"",
80
+ ),
81
+ (
82
+ serde_json::to_string(&StopOutcome::Stopped).unwrap(),
83
+ "\"stopped\"",
84
+ ),
85
+ (
86
+ serde_json::to_string(&TickStopReason::TmuxSessionMissing).unwrap(),
87
+ "\"tmux_session_missing\"",
88
+ ),
89
+ (
90
+ serde_json::to_string(&TickStopReason::PersistenceDegraded).unwrap(),
91
+ "\"persistence_degraded\"",
92
+ ),
93
+ (
94
+ serde_json::to_string(&MetadataSource::Boot).unwrap(),
95
+ "\"boot\"",
96
+ ),
97
+ (
98
+ serde_json::to_string(&MetadataSource::Start).unwrap(),
99
+ "\"start\"",
100
+ ),
101
+ (
102
+ serde_json::to_string(&AbnormalDecision::Skip).unwrap(),
103
+ "\"skip\"",
104
+ ),
105
+ (
106
+ serde_json::to_string(&AbnormalDecision::NotifyBlacklist).unwrap(),
107
+ "\"notify_blacklist\"",
108
+ ),
109
+ (
110
+ serde_json::to_string(&AbnormalDecision::NotifyDefault).unwrap(),
111
+ "\"notify_default\"",
112
+ ),
113
+ (
114
+ serde_json::to_string(&WholeTeamGoneClass::Alive).unwrap(),
115
+ "\"alive\"",
116
+ ),
117
+ (
118
+ serde_json::to_string(&WholeTeamGoneClass::CleanShutdown).unwrap(),
119
+ "\"clean_shutdown\"",
120
+ ),
121
+ (
122
+ serde_json::to_string(&WholeTeamGoneClass::RestartInProgress).unwrap(),
123
+ "\"restart_in_progress\"",
124
+ ),
125
+ (
126
+ serde_json::to_string(&WholeTeamGoneClass::UnexpectedExit).unwrap(),
127
+ "\"unexpected_exit\"",
128
+ ),
57
129
  ];
58
130
  for (got, want) in cases {
59
131
  assert_eq!(got, want);
@@ -65,26 +137,64 @@ fn coordinator_event_tags_are_byte_stable() {
65
137
  // 逐一钉 events.jsonl tag(events.py 稳定契约)。
66
138
  let pid = Pid(4242);
67
139
  let cases: Vec<(CoordinatorEvent, &str)> = vec![
68
- (CoordinatorEvent::Boot { workspace: "/w".into(), once: true }, "coordinator.boot"),
69
- (CoordinatorEvent::Started { pid, log: "/l".into() }, "coordinator.started"),
140
+ (
141
+ CoordinatorEvent::Boot {
142
+ workspace: "/w".into(),
143
+ once: true,
144
+ },
145
+ "coordinator.boot",
146
+ ),
147
+ (
148
+ CoordinatorEvent::Started {
149
+ pid,
150
+ log: "/l".into(),
151
+ },
152
+ "coordinator.started",
153
+ ),
70
154
  (CoordinatorEvent::Stopped { pid }, "coordinator.stopped"),
71
155
  (CoordinatorEvent::Exit { stop: true }, "coordinator.exit"),
72
- (CoordinatorEvent::SessionMissing { session: "s".into() }, "coordinator.session_missing"),
73
156
  (
74
- CoordinatorEvent::OrphanSelfTerminate { initial_ppid: 9, current_ppid: 1, workspace: "/w".into() },
157
+ CoordinatorEvent::SessionMissing {
158
+ session: "s".into(),
159
+ },
160
+ "coordinator.session_missing",
161
+ ),
162
+ (
163
+ CoordinatorEvent::OrphanSelfTerminate {
164
+ initial_ppid: 9,
165
+ current_ppid: 1,
166
+ workspace: "/w".into(),
167
+ },
75
168
  "coordinator.orphan_self_terminate",
76
169
  ),
77
170
  (
78
- CoordinatorEvent::TickError { error: "boom".into(), exc_type: "OSError".into(), consecutive_failures: 1, next_sleep_sec: 5.0 },
171
+ CoordinatorEvent::TickError {
172
+ error: "boom".into(),
173
+ exc_type: "OSError".into(),
174
+ consecutive_failures: 1,
175
+ next_sleep_sec: 5.0,
176
+ },
79
177
  "coordinator.tick_error",
80
178
  ),
81
179
  (
82
- CoordinatorEvent::TickErrorSuppressed { consecutive_failures: 2, next_sleep_sec: 10.0 },
180
+ CoordinatorEvent::TickErrorSuppressed {
181
+ consecutive_failures: 2,
182
+ next_sleep_sec: 10.0,
183
+ },
83
184
  "coordinator.tick_error.suppressed",
84
185
  ),
85
- (CoordinatorEvent::TickRecovered { consecutive_failures: 3 }, "coordinator.tick_recovered"),
86
186
  (
87
- CoordinatorEvent::RestartIncompatible { pid: Some(pid), expected_protocol: 2, expected_schema: 3 },
187
+ CoordinatorEvent::TickRecovered {
188
+ consecutive_failures: 3,
189
+ },
190
+ "coordinator.tick_recovered",
191
+ ),
192
+ (
193
+ CoordinatorEvent::RestartIncompatible {
194
+ pid: Some(pid),
195
+ expected_protocol: 2,
196
+ expected_schema: 3,
197
+ },
88
198
  "coordinator.restart_incompatible",
89
199
  ),
90
200
  (
@@ -92,7 +202,10 @@ fn coordinator_event_tags_are_byte_stable() {
92
202
  "coordinator.restart_incompatible_stop_failed",
93
203
  ),
94
204
  (
95
- CoordinatorEvent::SchemaIncompatible { table: Some("messages".into()), missing_columns: vec!["owner_team_id".into()] },
205
+ CoordinatorEvent::SchemaIncompatible {
206
+ table: Some("messages".into()),
207
+ missing_columns: vec!["owner_team_id".into()],
208
+ },
96
209
  "coordinator.schema_incompatible",
97
210
  ),
98
211
  (
@@ -106,17 +219,33 @@ fn coordinator_event_tags_are_byte_stable() {
106
219
  "idle_takeover.unknown_persistent",
107
220
  ),
108
221
  (
109
- CoordinatorEvent::AbnormalNotify { signature: "sig".into(), turn_id: None, decision: AbnormalDecision::NotifyDefault },
222
+ CoordinatorEvent::AbnormalNotify {
223
+ signature: "sig".into(),
224
+ turn_id: None,
225
+ decision: AbnormalDecision::NotifyDefault,
226
+ },
110
227
  "abnormal.notify",
111
228
  ),
112
229
  (
113
- CoordinatorEvent::AbnormalWholeTeamGone { classification: WholeTeamGoneClass::UnexpectedExit },
230
+ CoordinatorEvent::AbnormalWholeTeamGone {
231
+ classification: WholeTeamGoneClass::UnexpectedExit,
232
+ },
114
233
  "abnormal.whole_team_gone",
115
234
  ),
116
- (CoordinatorEvent::LeaderNotificationLogPruned { removed: 7 }, "leader_notification.log_pruned"),
117
- (CoordinatorEvent::LeaderNotificationPruneFailed { error: "io".into() }, "leader_notification.prune_failed"),
118
235
  (
119
- CoordinatorEvent::RuntimeStateSaveFailed { phase: "tick_end".into(), error: "replace".into(), exc_type: "OSError".into() },
236
+ CoordinatorEvent::LeaderNotificationLogPruned { removed: 7 },
237
+ "leader_notification.log_pruned",
238
+ ),
239
+ (
240
+ CoordinatorEvent::LeaderNotificationPruneFailed { error: "io".into() },
241
+ "leader_notification.prune_failed",
242
+ ),
243
+ (
244
+ CoordinatorEvent::RuntimeStateSaveFailed {
245
+ phase: "tick_end".into(),
246
+ error: "replace".into(),
247
+ exc_type: "OSError".into(),
248
+ },
120
249
  "runtime.state.save_failed",
121
250
  ),
122
251
  ];
@@ -157,11 +286,20 @@ fn unknown_persistent_event_provider_none_is_null_not_missing() {
157
286
  let json = serde_json::to_value(&evt).unwrap();
158
287
  assert_eq!(json["event"], "idle_takeover.unknown_persistent");
159
288
  assert_eq!(json["node_id"], "w7");
160
- assert!(json.get("provider").is_some(), "provider key MUST be present (None→null)");
289
+ assert!(
290
+ json.get("provider").is_some(),
291
+ "provider key MUST be present (None→null)"
292
+ );
161
293
  assert!(json["provider"].is_null(), "provider None → JSON null");
162
- assert!(json.get("auth_mode").is_some(), "auth_mode key MUST be present");
294
+ assert!(
295
+ json.get("auth_mode").is_some(),
296
+ "auth_mode key MUST be present"
297
+ );
163
298
  assert!(json["auth_mode"].is_null(), "auth_mode None → JSON null");
164
- assert!(json.get("rollout_path").is_some(), "rollout_path key MUST be present");
299
+ assert!(
300
+ json.get("rollout_path").is_some(),
301
+ "rollout_path key MUST be present"
302
+ );
165
303
  assert!(json["rollout_path"].is_null());
166
304
  assert_eq!(json["consecutive_ticks"], 72);
167
305
  }
@@ -12,7 +12,11 @@ use super::*;
12
12
  fn daemon_ws() -> (WorkspacePath, std::path::PathBuf) {
13
13
  use std::sync::atomic::{AtomicU64, Ordering};
14
14
  static N: AtomicU64 = AtomicU64::new(0);
15
- let dir = std::env::temp_dir().join(format!("ta-rs-daemon-{}-{}", std::process::id(), N.fetch_add(1, Ordering::Relaxed)));
15
+ let dir = std::env::temp_dir().join(format!(
16
+ "ta-rs-daemon-{}-{}",
17
+ std::process::id(),
18
+ N.fetch_add(1, Ordering::Relaxed)
19
+ ));
16
20
  std::fs::create_dir_all(crate::model::paths::runtime_dir(&dir)).unwrap();
17
21
  let _ = crate::message_store::MessageStore::open(&dir).unwrap(); // create the schema (schema_ok)
18
22
  (WorkspacePath::new(dir.clone()), dir)
@@ -35,8 +39,15 @@ fn coordinator_health_running_with_matching_metadata_is_ok() {
35
39
  write_coordinator_metadata(&wp, me, MetadataSource::Boot).unwrap();
36
40
  std::fs::write(coordinator_pid_path(&wp), me.0.to_string()).unwrap();
37
41
  let h = coordinator_health(&wp);
38
- assert_eq!(h.status, CoordinatorHealthStatus::Running, "a live pid -> status running");
39
- assert!(h.metadata_ok, "pid+protocol+schema all match -> metadata_ok");
42
+ assert_eq!(
43
+ h.status,
44
+ CoordinatorHealthStatus::Running,
45
+ "a live pid -> status running"
46
+ );
47
+ assert!(
48
+ h.metadata_ok,
49
+ "pid+protocol+schema all match -> metadata_ok"
50
+ );
40
51
  assert!(h.ok, "running ∧ metadata_ok ∧ schema_ok -> healthy");
41
52
  }
42
53
 
@@ -48,7 +59,11 @@ fn coordinator_health_dead_pid_is_stale_not_ok() {
48
59
  write_coordinator_metadata(&wp, dead, MetadataSource::Boot).unwrap();
49
60
  std::fs::write(coordinator_pid_path(&wp), dead.0.to_string()).unwrap();
50
61
  let h = coordinator_health(&wp);
51
- assert_eq!(h.status, CoordinatorHealthStatus::Stale, "a dead pid -> status stale");
62
+ assert_eq!(
63
+ h.status,
64
+ CoordinatorHealthStatus::Stale,
65
+ "a dead pid -> status stale"
66
+ );
52
67
  assert!(!h.ok, "a stale daemon is not healthy");
53
68
  }
54
69
 
@@ -60,7 +75,11 @@ fn start_coordinator_when_healthy_is_already_running_no_spawn() {
60
75
  write_coordinator_metadata(&wp, me, MetadataSource::Boot).unwrap();
61
76
  std::fs::write(coordinator_pid_path(&wp), me.0.to_string()).unwrap();
62
77
  let report = start_coordinator(&wp).expect("start_coordinator");
63
- assert_eq!(report.status, StartOutcome::AlreadyRunning, "a healthy coordinator -> AlreadyRunning (no spawn)");
78
+ assert_eq!(
79
+ report.status,
80
+ StartOutcome::AlreadyRunning,
81
+ "a healthy coordinator -> AlreadyRunning (no spawn)"
82
+ );
64
83
  assert!(report.ok);
65
84
  assert_eq!(report.pid, Some(me));
66
85
  }
@@ -86,10 +105,22 @@ fn start_coordinator_fresh_workspace_decides_started() {
86
105
  single-tick testing"]
87
106
  fn run_daemon_once_writes_boot_metadata_and_returns_ok() {
88
107
  let (wp, dir) = daemon_ws();
89
- crate::state::persist::save_runtime_state(&dir, &serde_json::json!({"session_name": "team-x", "agents": {}})).unwrap();
90
- let r = run_daemon(DaemonArgs { workspace: wp.clone(), once: true, tick_interval_sec: None, team_key: None });
108
+ crate::state::persist::save_runtime_state(
109
+ &dir,
110
+ &serde_json::json!({"session_name": "team-x", "agents": {}}),
111
+ )
112
+ .unwrap();
113
+ let r = run_daemon(DaemonArgs {
114
+ workspace: wp.clone(),
115
+ once: true,
116
+ tick_interval_sec: None,
117
+ team_key: None,
118
+ });
91
119
  assert!(r.is_ok(), "run_daemon --once must return Ok; got {r:?}");
92
- assert!(coordinator_meta_path(&wp).exists(), "run_daemon must write the coordinator boot metadata");
120
+ assert!(
121
+ coordinator_meta_path(&wp).exists(),
122
+ "run_daemon must write the coordinator boot metadata"
123
+ );
93
124
  }
94
125
 
95
126
  // ═════════════════════════════════════════════════════════════════════════
@@ -403,8 +434,11 @@ fn tick_genuine_fast_session_miss_still_stops() {
403
434
  // genuinely gone) => {ok:false, stop:true, reason:tmux_session_missing}. The OTHER side of the
404
435
  // fork from the timeout case: a definitive miss MUST still stop the daemon. LOCK (already GREEN):
405
436
  // guards the genuine-miss stop from being swallowed when the timeout tolerance is added.
406
- let (coord, _dir, _seen) =
407
- coord_over_staged_tmux("team-spine", vec![RunnerStep::Exit(false)], RunnerStep::Exit(false));
437
+ let (coord, _dir, _seen) = coord_over_staged_tmux(
438
+ "team-spine",
439
+ vec![RunnerStep::Exit(false)],
440
+ RunnerStep::Exit(false),
441
+ );
408
442
  let report = coord
409
443
  .tick()
410
444
  .expect("a definitive miss is a typed stop report, not an Err");
@@ -453,15 +487,21 @@ fn run_daemon_backs_off_on_transient_tick_err_then_recovers_without_exiting() {
453
487
  let err_idx = tags
454
488
  .iter()
455
489
  .position(|t| *t == "coordinator.tick_error")
456
- .unwrap_or_else(|| panic!("a transient tick Err must log coordinator.tick_error; got {tags:?}"));
490
+ .unwrap_or_else(|| {
491
+ panic!("a transient tick Err must log coordinator.tick_error; got {tags:?}")
492
+ });
457
493
  let rec_idx = tags
458
494
  .iter()
459
495
  .position(|t| *t == "coordinator.tick_recovered")
460
- .unwrap_or_else(|| panic!("the recovering Ok tick must log coordinator.tick_recovered; got {tags:?}"));
496
+ .unwrap_or_else(|| {
497
+ panic!("the recovering Ok tick must log coordinator.tick_recovered; got {tags:?}")
498
+ });
461
499
  let exit_idx = tags
462
500
  .iter()
463
501
  .position(|t| *t == "coordinator.exit")
464
- .unwrap_or_else(|| panic!("the daemon must log coordinator.exit once it stops; got {tags:?}"));
502
+ .unwrap_or_else(|| {
503
+ panic!("the daemon must log coordinator.exit once it stops; got {tags:?}")
504
+ });
465
505
  assert!(
466
506
  err_idx < rec_idx,
467
507
  "tick_recovered must FOLLOW tick_error (backoff then recover); got {tags:?}"
@@ -20,17 +20,38 @@ impl Transport for CapturingTransport {
20
20
  fn kind(&self) -> BackendKind {
21
21
  BackendKind::Tmux
22
22
  }
23
- fn spawn_first(&self, _s: &SessionName, _w: &WindowName, _a: &[String], _c: &std::path::Path, _e: &std::collections::BTreeMap<String, String>) -> Result<SpawnResult, TransportError> {
23
+ fn spawn_first(
24
+ &self,
25
+ _s: &SessionName,
26
+ _w: &WindowName,
27
+ _a: &[String],
28
+ _c: &std::path::Path,
29
+ _e: &std::collections::BTreeMap<String, String>,
30
+ ) -> Result<SpawnResult, TransportError> {
24
31
  unimplemented!("not reached")
25
32
  }
26
- fn spawn_into(&self, _s: &SessionName, _w: &WindowName, _a: &[String], _c: &std::path::Path, _e: &std::collections::BTreeMap<String, String>) -> Result<SpawnResult, TransportError> {
33
+ fn spawn_into(
34
+ &self,
35
+ _s: &SessionName,
36
+ _w: &WindowName,
37
+ _a: &[String],
38
+ _c: &std::path::Path,
39
+ _e: &std::collections::BTreeMap<String, String>,
40
+ ) -> Result<SpawnResult, TransportError> {
27
41
  unimplemented!("not reached")
28
42
  }
29
- fn inject(&self, _t: &Target, _p: &InjectPayload, _s: Key, _b: bool) -> Result<InjectReport, TransportError> {
43
+ fn inject(
44
+ &self,
45
+ _t: &Target,
46
+ _p: &InjectPayload,
47
+ _s: Key,
48
+ _b: bool,
49
+ ) -> Result<InjectReport, TransportError> {
30
50
  Ok(InjectReport {
31
51
  stage_reached: crate::transport::InjectStage::Submit,
32
52
  inject_verification: crate::transport::InjectVerification::CaptureContainsToken,
33
- submit_verification: crate::transport::SubmitVerification::EnterSentWithoutPlaceholderCheck,
53
+ submit_verification:
54
+ crate::transport::SubmitVerification::EnterSentWithoutPlaceholderCheck,
34
55
  turn_verification: crate::transport::TurnVerification::NotYetObserved,
35
56
  attempts: 1,
36
57
  submit_diagnostics: None,
@@ -40,7 +61,10 @@ impl Transport for CapturingTransport {
40
61
  Ok(())
41
62
  }
42
63
  fn capture(&self, _t: &Target, range: CaptureRange) -> Result<CapturedText, TransportError> {
43
- Ok(CapturedText { text: self.scrollback.clone(), range })
64
+ Ok(CapturedText {
65
+ text: self.scrollback.clone(),
66
+ range,
67
+ })
44
68
  }
45
69
  fn query(&self, _t: &Target, _f: PaneField) -> Result<Option<String>, TransportError> {
46
70
  Ok(None)
@@ -57,7 +81,12 @@ impl Transport for CapturingTransport {
57
81
  fn list_windows(&self, _s: &SessionName) -> Result<Vec<WindowName>, TransportError> {
58
82
  Ok(vec![WindowName::new("w1")])
59
83
  }
60
- fn set_session_env(&self, _s: &SessionName, _k: &str, _v: &str) -> Result<SetEnvOutcome, TransportError> {
84
+ fn set_session_env(
85
+ &self,
86
+ _s: &SessionName,
87
+ _k: &str,
88
+ _v: &str,
89
+ ) -> Result<SetEnvOutcome, TransportError> {
61
90
  Ok(SetEnvOutcome::Applied)
62
91
  }
63
92
  fn kill_session(&self, _s: &SessionName) -> Result<(), TransportError> {
@@ -74,7 +103,10 @@ impl Transport for CapturingTransport {
74
103
  /// Build a Coordinator over a real seeded workspace (truthy session_name + the given agents map) with
75
104
  /// the CapturingTransport staging `scrollback` for every pane. Returns the workspace dir so the test
76
105
  /// can load_runtime_state after the tick.
77
- fn seeded_health_coord(agents: serde_json::Value, scrollback: &str) -> (Coordinator, std::path::PathBuf) {
106
+ fn seeded_health_coord(
107
+ agents: serde_json::Value,
108
+ scrollback: &str,
109
+ ) -> (Coordinator, std::path::PathBuf) {
78
110
  use std::sync::atomic::{AtomicU64, Ordering};
79
111
  static N: AtomicU64 = AtomicU64::new(0);
80
112
  let dir = std::env::temp_dir().join(format!(
@@ -93,7 +125,9 @@ fn seeded_health_coord(agents: serde_json::Value, scrollback: &str) -> (Coordina
93
125
  let coord = Coordinator::for_test(
94
126
  ws,
95
127
  reg,
96
- Box::new(CapturingTransport { scrollback: scrollback.to_string() }),
128
+ Box::new(CapturingTransport {
129
+ scrollback: scrollback.to_string(),
130
+ }),
97
131
  None,
98
132
  None,
99
133
  );
@@ -136,11 +170,18 @@ fn spine2_sync_health_classifies_idle_scrollback() {
136
170
  // NEVER as idle. Today: no activity written.
137
171
  #[test]
138
172
  fn spine2_sync_health_unknown_scrollback_never_idle() {
139
- let (coord, dir) = seeded_health_coord(one_agent("codex"), "garbled noise xyz 12345 no recognizable signal");
173
+ let (coord, dir) = seeded_health_coord(
174
+ one_agent("codex"),
175
+ "garbled noise xyz 12345 no recognizable signal",
176
+ );
140
177
  coord.tick().expect("tick");
141
178
  let status = agent_activity_status(&dir, "w1");
142
179
  assert!(status.is_some(), "sync_health must classify the agent (write activity); today the probe writes nothing. got {status:?}");
143
- assert_ne!(status.as_deref(), Some("idle"), "§11: an UNKNOWN scrollback must NEVER be classified idle");
180
+ assert_ne!(
181
+ status.as_deref(),
182
+ Some("idle"),
183
+ "§11: an UNKNOWN scrollback must NEVER be classified idle"
184
+ );
144
185
  }
145
186
 
146
187
  // P0 §11 — a WORKING scrollback classifies the agent but never idle.
@@ -149,8 +190,15 @@ fn spine2_sync_health_working_scrollback_never_idle() {
149
190
  let (coord, dir) = seeded_health_coord(one_agent("codex"), "Working (5s · esc to interrupt)");
150
191
  coord.tick().expect("tick");
151
192
  let status = agent_activity_status(&dir, "w1");
152
- assert!(status.is_some(), "sync_health must classify a working agent; today no activity. got {status:?}");
153
- assert_ne!(status.as_deref(), Some("idle"), "§11: a WORKING scrollback must not be idle");
193
+ assert!(
194
+ status.is_some(),
195
+ "sync_health must classify a working agent; today no activity. got {status:?}"
196
+ );
197
+ assert_ne!(
198
+ status.as_deref(),
199
+ Some("idle"),
200
+ "§11: a WORKING scrollback must not be idle"
201
+ );
154
202
  }
155
203
 
156
204
  // P1 — sync_health records last_output_at on a pane delta (so detect_stuck / take-over downstream can
@@ -172,7 +220,11 @@ fn spine2_sync_health_records_last_output_at() {
172
220
  fn spine2_capture_missing_captures_session_id_for_missing_agent() {
173
221
  use std::sync::atomic::{AtomicU64, Ordering};
174
222
  static N: AtomicU64 = AtomicU64::new(0);
175
- let tdir = std::env::temp_dir().join(format!("ta-rs-health-tx-{}-{}", std::process::id(), N.fetch_add(1, Ordering::Relaxed)));
223
+ let tdir = std::env::temp_dir().join(format!(
224
+ "ta-rs-health-tx-{}-{}",
225
+ std::process::id(),
226
+ N.fetch_add(1, Ordering::Relaxed)
227
+ ));
176
228
  std::fs::create_dir_all(&tdir).unwrap();
177
229
  // lane-046-capture-gap: Claude no-expected_session_id capture now requires
178
230
  // positive_agent_id_match (TEAM_AGENT_ID=<id>) OR agent_path_match. Without
@@ -195,7 +247,9 @@ fn spine2_capture_missing_captures_session_id_for_missing_agent() {
195
247
  "capture_missing must capture+persist a session_id for an agent with a discoverable transcript; today it's a probe"
196
248
  );
197
249
  assert_eq!(
198
- agent_field(&dir, "w2", "session_id").and_then(|v| v.as_str().map(str::to_string)).as_deref(),
250
+ agent_field(&dir, "w2", "session_id")
251
+ .and_then(|v| v.as_str().map(str::to_string))
252
+ .as_deref(),
199
253
  Some("existing-sess"),
200
254
  "an agent that already has a session_id must be untouched"
201
255
  );
@@ -208,7 +262,9 @@ fn spine2_capture_missing_captures_session_id_for_missing_agent() {
208
262
  fn spine2_sync_health_working_status_delivers_same_tick() {
209
263
  let (coord, dir) = seeded_health_coord(one_agent("codex"), "Working (5s · esc to interrupt)");
210
264
  let store = MessageStore::open(&dir).unwrap();
211
- let mid = store.create_message(Some("t"), "leader", "w1", "hi", None, true, None).unwrap();
265
+ let mid = store
266
+ .create_message(Some("t"), "leader", "w1", "hi", None, true, None)
267
+ .unwrap();
212
268
  let conn = crate::db::schema::open_db(store.db_path()).unwrap();
213
269
  conn.execute(
214
270
  "insert into agent_health(owner_team_id, agent_id, status, updated_at)
@@ -223,7 +279,9 @@ fn spine2_sync_health_working_status_delivers_same_tick() {
223
279
  coord.tick().expect("tick");
224
280
  let events = read_event_log_dir(&dir);
225
281
  assert!(
226
- !events.iter().any(|e| e.get("event").and_then(|v| v.as_str()) == Some("send.deferred_busy")),
282
+ !events
283
+ .iter()
284
+ .any(|e| e.get("event").and_then(|v| v.as_str()) == Some("send.deferred_busy")),
227
285
  "turn-level WORKING must not trigger lifecycle busy deferral; got {events:?}"
228
286
  );
229
287
  assert!(
@@ -250,7 +308,9 @@ fn spine2_sync_health_working_status_delivers_same_tick() {
250
308
  fn contract_working_worker_stays_alive_and_deliverable_in_real_tick() {
251
309
  let (coord, dir) = seeded_health_coord(one_agent("codex"), "Working (5s · esc to interrupt)");
252
310
  let store = MessageStore::open(&dir).unwrap();
253
- let _mid = store.create_message(Some("t"), "leader", "w1", "hi", None, true, None).unwrap();
311
+ let _mid = store
312
+ .create_message(Some("t"), "leader", "w1", "hi", None, true, None)
313
+ .unwrap();
254
314
  drop(store);
255
315
  coord.tick().expect("tick");
256
316
  let status = agent_field(&dir, "w1", "status").and_then(|v| v.as_str().map(str::to_string));
@@ -406,8 +466,7 @@ fn rm039_stat001_jsonl_close_turn_preserves_e47_idle_classification() {
406
466
  // The pane Tail(40) still shows the live working spinner — the E47
407
467
  // stale-working scenario. The JSONL `task_complete` MUST close the
408
468
  // turn anyway.
409
- let (coord, dir) =
410
- seeded_health_coord(agents, "Working (5s · esc to interrupt)\n• Working\n");
469
+ let (coord, dir) = seeded_health_coord(agents, "Working (5s · esc to interrupt)\n• Working\n");
411
470
  coord.tick().expect("tick");
412
471
  let status = agent_activity_status(&dir, "stat-worker");
413
472
  assert_eq!(