@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
@@ -3,8 +3,8 @@
3
3
 
4
4
  use super::helpers::parse_jsonl_records;
5
5
  use super::types::{
6
- ClassifyResult, ClassifySource, FactKind, FaultFact, NoPingReason, ProcessLiveness, ProviderError,
7
- RemindResult, Signature, TurnId, TurnState,
6
+ ClassifyResult, ClassifySource, FactKind, FaultFact, NoPingReason, ProcessLiveness,
7
+ ProviderError, RemindResult, Signature, TurnId, TurnState,
8
8
  };
9
9
  use super::Provider;
10
10
 
@@ -116,14 +116,20 @@ fn collect_background_open_ids(value: &serde_json::Value, out: &mut Vec<String>)
116
116
  match value {
117
117
  serde_json::Value::String(s) => {
118
118
  for piece in s.split(MARKER).skip(1) {
119
- let id: String = piece.chars().take_while(|c| !c.is_whitespace() && *c != '\n').collect();
119
+ let id: String = piece
120
+ .chars()
121
+ .take_while(|c| !c.is_whitespace() && *c != '\n')
122
+ .collect();
120
123
  if !id.is_empty() {
121
124
  out.push(id);
122
125
  }
123
126
  }
124
127
  }
125
128
  serde_json::Value::Object(map) => {
126
- if let Some(id) = map.get("backgroundTaskId").and_then(serde_json::Value::as_str) {
129
+ if let Some(id) = map
130
+ .get("backgroundTaskId")
131
+ .and_then(serde_json::Value::as_str)
132
+ {
127
133
  if !id.is_empty() {
128
134
  out.push(id.to_string());
129
135
  }
@@ -147,9 +153,15 @@ fn collect_background_close_ids(value: &serde_json::Value, out: &mut Vec<String>
147
153
  // Form B: <task-notification> wrapper in a string with a status tag.
148
154
  if let serde_json::Value::Object(map) = value {
149
155
  if let Some(bash) = map.get("bashOutput").and_then(serde_json::Value::as_object) {
150
- let status = bash.get("status").and_then(serde_json::Value::as_str).unwrap_or("");
156
+ let status = bash
157
+ .get("status")
158
+ .and_then(serde_json::Value::as_str)
159
+ .unwrap_or("");
151
160
  if matches!(status, "completed" | "failed" | "killed") {
152
- if let Some(id) = bash.get("backgroundTaskId").and_then(serde_json::Value::as_str) {
161
+ if let Some(id) = bash
162
+ .get("backgroundTaskId")
163
+ .and_then(serde_json::Value::as_str)
164
+ {
153
165
  if !id.is_empty() {
154
166
  out.push(id.to_string());
155
167
  }
@@ -236,37 +248,90 @@ pub fn evaluate_takeover_reminder(
236
248
  }
237
249
  }
238
250
  TurnState::Working => {
239
- return Ok(remind(false, NoPingReason::Node(TurnState::Working), Vec::new(), None));
251
+ return Ok(remind(
252
+ false,
253
+ NoPingReason::Node(TurnState::Working),
254
+ Vec::new(),
255
+ None,
256
+ ));
240
257
  }
241
258
  TurnState::BlockedOnHuman => {
242
- return Ok(remind(false, NoPingReason::Node(TurnState::BlockedOnHuman), Vec::new(), None));
259
+ return Ok(remind(
260
+ false,
261
+ NoPingReason::Node(TurnState::BlockedOnHuman),
262
+ Vec::new(),
263
+ None,
264
+ ));
243
265
  }
244
266
  TurnState::Abnormal => {
245
- return Ok(remind(false, NoPingReason::Node(TurnState::Abnormal), Vec::new(), None));
267
+ return Ok(remind(
268
+ false,
269
+ NoPingReason::Node(TurnState::Abnormal),
270
+ Vec::new(),
271
+ None,
272
+ ));
246
273
  }
247
274
  TurnState::Unknown => {
248
- return Ok(remind(false, NoPingReason::Node(TurnState::Unknown), Vec::new(), None));
275
+ return Ok(remind(
276
+ false,
277
+ NoPingReason::Node(TurnState::Unknown),
278
+ Vec::new(),
279
+ None,
280
+ ));
249
281
  }
250
282
  }
251
283
  }
252
284
 
253
285
  let Some(ms) = monitor_state else {
254
- return Ok(remind(false, NoPingReason::NotArmedNoWorkerTurn, Vec::new(), None));
286
+ return Ok(remind(
287
+ false,
288
+ NoPingReason::NotArmedNoWorkerTurn,
289
+ Vec::new(),
290
+ None,
291
+ ));
255
292
  };
256
- if ms.get("opened_worker_turn_since_ack").and_then(serde_json::Value::as_bool) != Some(true) {
257
- return Ok(remind(false, NoPingReason::NotArmedNoWorkerTurn, Vec::new(), None));
293
+ if ms
294
+ .get("opened_worker_turn_since_ack")
295
+ .and_then(serde_json::Value::as_bool)
296
+ != Some(true)
297
+ {
298
+ return Ok(remind(
299
+ false,
300
+ NoPingReason::NotArmedNoWorkerTurn,
301
+ Vec::new(),
302
+ None,
303
+ ));
258
304
  }
259
305
  if ms.get("suppressed").and_then(serde_json::Value::as_bool) == Some(true) {
260
306
  return Ok(remind(false, NoPingReason::Acknowledged, Vec::new(), None));
261
307
  }
262
308
  let Some(all_idle_since) = ms.get("all_idle_since").and_then(serde_json::Value::as_f64) else {
263
- return Ok(remind(false, NoPingReason::NotArmedNoWorkerTurn, Vec::new(), None));
309
+ return Ok(remind(
310
+ false,
311
+ NoPingReason::NotArmedNoWorkerTurn,
312
+ Vec::new(),
313
+ None,
314
+ ));
264
315
  };
265
- if ms.get("pinged_for_episode").and_then(serde_json::Value::as_f64) == Some(all_idle_since) {
266
- return Ok(remind(false, NoPingReason::AlreadyPingedThisEpisode, Vec::new(), None));
316
+ if ms
317
+ .get("pinged_for_episode")
318
+ .and_then(serde_json::Value::as_f64)
319
+ == Some(all_idle_since)
320
+ {
321
+ return Ok(remind(
322
+ false,
323
+ NoPingReason::AlreadyPingedThisEpisode,
324
+ Vec::new(),
325
+ None,
326
+ ));
267
327
  }
268
328
  if now_monotonic - all_idle_since < debounce_seconds {
269
- return Ok(remind(false, NoPingReason::DebounceActive, Vec::new(), None));
329
+ return Ok(remind(
330
+ false,
331
+ NoPingReason::DebounceActive,
332
+ Vec::new(),
333
+ None,
334
+ ));
270
335
  }
271
336
  Ok(remind(
272
337
  true,
@@ -301,7 +366,10 @@ fn classify_result(
301
366
  }
302
367
  }
303
368
 
304
- fn extract_lifecycle_facts(provider: Provider, records: &[serde_json::Value]) -> Vec<LifecycleFact> {
369
+ fn extract_lifecycle_facts(
370
+ provider: Provider,
371
+ records: &[serde_json::Value],
372
+ ) -> Vec<LifecycleFact> {
305
373
  records
306
374
  .iter()
307
375
  .filter_map(|record| match provider {
@@ -328,7 +396,9 @@ fn codex_latest_explicit_error_fact(record: &serde_json::Value) -> Option<FaultF
328
396
  }
329
397
  Some(FaultFact::new(
330
398
  Signature::new("turn_failed"),
331
- turn.get("id").and_then(serde_json::Value::as_str).map(TurnId::new),
399
+ turn.get("id")
400
+ .and_then(serde_json::Value::as_str)
401
+ .map(TurnId::new),
332
402
  FactKind::Failed,
333
403
  ))
334
404
  }
@@ -431,29 +501,43 @@ fn claude_lifecycle_fact(record: &serde_json::Value) -> Option<LifecycleFact> {
431
501
  ));
432
502
  }
433
503
  if record_type == Some("assistant") {
434
- let turn_id = record.get("requestId").and_then(serde_json::Value::as_str).map(TurnId::new);
504
+ let turn_id = record
505
+ .get("requestId")
506
+ .and_then(serde_json::Value::as_str)
507
+ .map(TurnId::new);
435
508
  let Some(stop_reason) = record
436
509
  .get("message")
437
510
  .and_then(|m| m.get("stop_reason"))
438
- .and_then(serde_json::Value::as_str) else {
439
- if record
440
- .get("message")
441
- .and_then(|m| m.get("content"))
442
- .and_then(serde_json::Value::as_array)
443
- .is_some()
444
- {
445
- return Some(lifecycle(
446
- FactKind::TurnOpen,
447
- turn_id,
448
- "assistant_in_flight",
449
- vec!["assistant_in_flight".to_string()],
450
- ));
451
- }
452
- return None;
453
- };
511
+ .and_then(serde_json::Value::as_str)
512
+ else {
513
+ if record
514
+ .get("message")
515
+ .and_then(|m| m.get("content"))
516
+ .and_then(serde_json::Value::as_array)
517
+ .is_some()
518
+ {
519
+ return Some(lifecycle(
520
+ FactKind::TurnOpen,
521
+ turn_id,
522
+ "assistant_in_flight",
523
+ vec!["assistant_in_flight".to_string()],
524
+ ));
525
+ }
526
+ return None;
527
+ };
454
528
  return match stop_reason {
455
- "tool_use" => Some(lifecycle(FactKind::TurnOpen, turn_id, "open_turn", Vec::new())),
456
- "end_turn" => Some(lifecycle(FactKind::TurnComplete, turn_id, "end_turn", Vec::new())),
529
+ "tool_use" => Some(lifecycle(
530
+ FactKind::TurnOpen,
531
+ turn_id,
532
+ "open_turn",
533
+ Vec::new(),
534
+ )),
535
+ "end_turn" => Some(lifecycle(
536
+ FactKind::TurnComplete,
537
+ turn_id,
538
+ "end_turn",
539
+ Vec::new(),
540
+ )),
457
541
  "stop_sequence" => Some(lifecycle(
458
542
  FactKind::TurnComplete,
459
543
  turn_id,
@@ -466,7 +550,10 @@ fn claude_lifecycle_fact(record: &serde_json::Value) -> Option<LifecycleFact> {
466
550
  if record_type == Some("user") && claude_user_interrupted(record) {
467
551
  return Some(lifecycle(
468
552
  FactKind::Interrupted,
469
- record.get("uuid").and_then(serde_json::Value::as_str).map(TurnId::new),
553
+ record
554
+ .get("uuid")
555
+ .and_then(serde_json::Value::as_str)
556
+ .map(TurnId::new),
470
557
  "user_interrupt",
471
558
  vec!["interrupted".to_string()],
472
559
  ));
@@ -585,9 +672,7 @@ fn claude_user_interrupted(record: &serde_json::Value) -> bool {
585
672
  .is_some_and(|items| {
586
673
  items.iter().any(|item| {
587
674
  item.get("type").and_then(serde_json::Value::as_str) == Some("text")
588
- && item
589
- .get("text")
590
- .and_then(serde_json::Value::as_str)
675
+ && item.get("text").and_then(serde_json::Value::as_str)
591
676
  == Some("[Request interrupted by user]")
592
677
  })
593
678
  })
@@ -38,7 +38,9 @@ pub(crate) fn claude_explicit_error_fact(record: &serde_json::Value) -> Option<F
38
38
  FactKind::Error,
39
39
  )
40
40
  .with_api_error_details(
41
- record.get("apiErrorStatus").and_then(serde_json::Value::as_i64),
41
+ record
42
+ .get("apiErrorStatus")
43
+ .and_then(serde_json::Value::as_i64),
42
44
  non_empty_string_field(record, "error"),
43
45
  non_empty_string_field(record, "requestId"),
44
46
  non_empty_string_field(record, "uuid"),
@@ -58,7 +60,9 @@ fn codex_explicit_error_fact(record: &serde_json::Value) -> Option<FaultFact> {
58
60
  }
59
61
  Some(FaultFact::new(
60
62
  Signature::new("turn_failed"),
61
- turn.get("id").and_then(serde_json::Value::as_str).map(TurnId::new),
63
+ turn.get("id")
64
+ .and_then(serde_json::Value::as_str)
65
+ .map(TurnId::new),
62
66
  FactKind::Failed,
63
67
  ))
64
68
  }
@@ -126,8 +130,14 @@ fn claude_record_is_assistant_api_error(record: &serde_json::Value) -> bool {
126
130
  .and_then(|message| message.get("role"))
127
131
  .and_then(serde_json::Value::as_str)
128
132
  == Some("assistant")
129
- && record.get("isApiErrorMessage").and_then(serde_json::Value::as_bool) == Some(true)
130
- && (record.get("apiErrorStatus").and_then(serde_json::Value::as_i64).is_some()
133
+ && record
134
+ .get("isApiErrorMessage")
135
+ .and_then(serde_json::Value::as_bool)
136
+ == Some(true)
137
+ && (record
138
+ .get("apiErrorStatus")
139
+ .and_then(serde_json::Value::as_i64)
140
+ .is_some()
131
141
  || non_empty_string_field(record, "error").is_some()
132
142
  || non_empty_string_field(record, "requestId").is_some())
133
143
  }
@@ -156,7 +166,9 @@ fn codex_fault_fact(record: &serde_json::Value) -> Option<FaultFact> {
156
166
  if turn.get("status").and_then(serde_json::Value::as_str) == Some("failed") {
157
167
  return Some(FaultFact::new(
158
168
  Signature::new("turn_failed"),
159
- turn.get("id").and_then(serde_json::Value::as_str).map(TurnId::new),
169
+ turn.get("id")
170
+ .and_then(serde_json::Value::as_str)
171
+ .map(TurnId::new),
160
172
  FactKind::Failed,
161
173
  ));
162
174
  }
@@ -22,10 +22,7 @@ pub(crate) fn find_session_id(record: &serde_json::Value) -> Option<String> {
22
22
  if let Some(s) = record.get("sessionId").and_then(serde_json::Value::as_str) {
23
23
  return Some(s.to_string());
24
24
  }
25
- if let Some(s) = record
26
- .get("session_id")
27
- .and_then(serde_json::Value::as_str)
28
- {
25
+ if let Some(s) = record.get("session_id").and_then(serde_json::Value::as_str) {
29
26
  return Some(s.to_string());
30
27
  }
31
28
  record
@@ -37,7 +34,11 @@ pub(crate) fn find_session_id(record: &serde_json::Value) -> Option<String> {
37
34
  .map(ToString::to_string)
38
35
  }
39
36
 
40
- pub(crate) fn patterns(idle: &str, processing: &str, error: &str) -> Result<StatusPatterns, ProviderError> {
37
+ pub(crate) fn patterns(
38
+ idle: &str,
39
+ processing: &str,
40
+ error: &str,
41
+ ) -> Result<StatusPatterns, ProviderError> {
41
42
  Ok(StatusPatterns {
42
43
  idle: compile_regex(idle)?,
43
44
  processing: compile_regex(processing)?,
@@ -28,10 +28,8 @@ const CLAUDE_TRUST_MARKERS: &[&str] = &[
28
28
  "Enter to confirm",
29
29
  ];
30
30
  const CLAUDE_READY_MARKERS: &[&str] = &["Claude Code"];
31
- pub const COPILOT_TRUST_PROMPT_MARKER: &str =
32
- include_str!("testdata/copilot-trust-prompt.txt");
33
- pub const COPILOT_READY_MARKER: &str =
34
- include_str!("testdata/copilot-ready-marker.txt");
31
+ pub const COPILOT_TRUST_PROMPT_MARKER: &str = include_str!("testdata/copilot-trust-prompt.txt");
32
+ pub const COPILOT_READY_MARKER: &str = include_str!("testdata/copilot-ready-marker.txt");
35
33
  const COPILOT_TRUST_MARKERS: &[&str] = &[COPILOT_TRUST_PROMPT_MARKER];
36
34
  const COPILOT_READY_MARKERS: &[&str] = &[COPILOT_READY_MARKER];
37
35
  const COPILOT_TRUST_TITLE: &str = "Confirm folder trust";
@@ -173,13 +171,9 @@ fn has_active_claude_yes_trust_shape(output: &str) -> bool {
173
171
  {
174
172
  return false;
175
173
  }
176
- output
177
- .lines()
178
- .map(str::trim_start)
179
- .any(|line| {
180
- line == "❯ 1. Yes, I trust this folder"
181
- || line == "> 1. Yes, I trust this folder"
182
- })
174
+ output.lines().map(str::trim_start).any(|line| {
175
+ line == "❯ 1. Yes, I trust this folder" || line == "> 1. Yes, I trust this folder"
176
+ })
183
177
  }
184
178
 
185
179
  fn has_claude_ready_shape(output: &str) -> bool {
@@ -352,7 +346,10 @@ pub fn codex_handle_startup_prompts(
352
346
  StartupScreenDecision::KeepPolling => sleep_between_polls(sleep_s),
353
347
  }
354
348
  }
355
- StartupPromptOutcome { handled, capture_error }
349
+ StartupPromptOutcome {
350
+ handled,
351
+ capture_error,
352
+ }
356
353
  }
357
354
 
358
355
  pub fn claude_handle_startup_prompts(
@@ -386,7 +383,10 @@ pub fn claude_handle_startup_prompts(
386
383
  }
387
384
  }
388
385
  }
389
- StartupPromptOutcome { handled, capture_error }
386
+ StartupPromptOutcome {
387
+ handled,
388
+ capture_error,
389
+ }
390
390
  }
391
391
 
392
392
  pub fn copilot_handle_startup_prompts(
@@ -420,11 +420,17 @@ pub fn copilot_handle_startup_prompts(
420
420
  }
421
421
  }
422
422
  }
423
- StartupPromptOutcome { handled, capture_error }
423
+ StartupPromptOutcome {
424
+ handled,
425
+ capture_error,
426
+ }
424
427
  }
425
428
 
426
429
  fn max_rfind(output: &str, needles: &[&str]) -> Option<usize> {
427
- needles.iter().filter_map(|needle| output.rfind(needle)).max()
430
+ needles
431
+ .iter()
432
+ .filter_map(|needle| output.rfind(needle))
433
+ .max()
428
434
  }
429
435
 
430
436
  fn is_more_recent(prompt_pos: Option<usize>, ready_pos: Option<usize>) -> bool {
@@ -449,8 +455,8 @@ mod tests {
449
455
  use crate::model::enums::PaneLiveness;
450
456
  use crate::transport::{
451
457
  AttachOutcome, BackendKind, CaptureRange, CapturedText, InjectPayload, InjectReport, Key,
452
- PaneField, PaneId, PaneInfo, SessionName, SetEnvOutcome, SpawnResult, Target, TransportError,
453
- WindowName,
458
+ PaneField, PaneId, PaneInfo, SessionName, SetEnvOutcome, SpawnResult, Target,
459
+ TransportError, WindowName,
454
460
  };
455
461
  use std::collections::BTreeMap;
456
462
  use std::path::Path;
@@ -483,7 +489,8 @@ mod tests {
483
489
  fn stale_trust_above_ready_is_not_answered_ready_wins() {
484
490
  // trust prompt FIRST, then a ready marker LATER => trust_pos < ready_pos => do NOT answer.
485
491
  // This is the positional-recency命门 a naive substring port gets wrong (would re-send Enter).
486
- let screen = format!("{TRUST_DIR}\n[trusted earlier]\n{READY_BANNER} ready\n{READY_PROMPT} ");
492
+ let screen =
493
+ format!("{TRUST_DIR}\n[trusted earlier]\n{READY_BANNER} ready\n{READY_PROMPT} ");
487
494
  assert_eq!(
488
495
  classify_codex_startup_screen(&screen),
489
496
  StartupScreenDecision::Ready,
@@ -519,7 +526,10 @@ mod tests {
519
526
  #[test]
520
527
  fn stale_update_above_ready_is_not_skipped_ready_wins() {
521
528
  let screen = format!("{UPDATE_AVAIL}\n{READY_BANNER}\n{READY_PROMPT} ");
522
- assert_eq!(classify_codex_startup_screen(&screen), StartupScreenDecision::Ready);
529
+ assert_eq!(
530
+ classify_codex_startup_screen(&screen),
531
+ StartupScreenDecision::Ready
532
+ );
523
533
  }
524
534
 
525
535
  // ── golden ORDER — update is checked BEFORE trust (both more recent) -> SkipUpdatePrompt wins ─────
@@ -528,7 +538,10 @@ mod tests {
528
538
  // both update + trust appear after the ready marker; golden runs maybe_skip_update_prompt
529
539
  // FIRST each iteration -> the screen resolves to SkipUpdatePrompt, not AnswerWorkspaceTrust.
530
540
  let screen = format!("{READY_BANNER}\n{TRUST_DIR}\n{UPDATE_AVAIL}\n");
531
- assert_eq!(classify_codex_startup_screen(&screen), StartupScreenDecision::SkipUpdatePrompt);
541
+ assert_eq!(
542
+ classify_codex_startup_screen(&screen),
543
+ StartupScreenDecision::SkipUpdatePrompt
544
+ );
532
545
  }
533
546
 
534
547
  // ── ready-only / neither ─────────────────────────────────────────────────────────────────────────
@@ -601,22 +614,50 @@ mod tests {
601
614
  fn kind(&self) -> BackendKind {
602
615
  BackendKind::Tmux
603
616
  }
604
- fn spawn_first(&self, _s: &SessionName, _w: &WindowName, _a: &[String], _c: &Path, _e: &BTreeMap<String, String>) -> Result<SpawnResult, TransportError> {
617
+ fn spawn_first(
618
+ &self,
619
+ _s: &SessionName,
620
+ _w: &WindowName,
621
+ _a: &[String],
622
+ _c: &Path,
623
+ _e: &BTreeMap<String, String>,
624
+ ) -> Result<SpawnResult, TransportError> {
605
625
  unimplemented!("not reached by startup-prompt loop")
606
626
  }
607
- fn spawn_into(&self, _s: &SessionName, _w: &WindowName, _a: &[String], _c: &Path, _e: &BTreeMap<String, String>) -> Result<SpawnResult, TransportError> {
627
+ fn spawn_into(
628
+ &self,
629
+ _s: &SessionName,
630
+ _w: &WindowName,
631
+ _a: &[String],
632
+ _c: &Path,
633
+ _e: &BTreeMap<String, String>,
634
+ ) -> Result<SpawnResult, TransportError> {
608
635
  unimplemented!("not reached by startup-prompt loop")
609
636
  }
610
- fn inject(&self, _t: &Target, _p: &InjectPayload, _submit: Key, _b: bool) -> Result<InjectReport, TransportError> {
637
+ fn inject(
638
+ &self,
639
+ _t: &Target,
640
+ _p: &InjectPayload,
641
+ _submit: Key,
642
+ _b: bool,
643
+ ) -> Result<InjectReport, TransportError> {
611
644
  unimplemented!("not reached")
612
645
  }
613
646
  fn send_keys(&self, _t: &Target, keys: &[Key]) -> Result<(), TransportError> {
614
647
  self.sent.lock().unwrap().push(keys.to_vec());
615
648
  Ok(())
616
649
  }
617
- fn capture(&self, _t: &Target, range: CaptureRange) -> Result<CapturedText, TransportError> {
650
+ fn capture(
651
+ &self,
652
+ _t: &Target,
653
+ range: CaptureRange,
654
+ ) -> Result<CapturedText, TransportError> {
618
655
  let mut q = self.screens.lock().unwrap();
619
- let text = if q.is_empty() { String::new() } else { q.remove(0) };
656
+ let text = if q.is_empty() {
657
+ String::new()
658
+ } else {
659
+ q.remove(0)
660
+ };
620
661
  Ok(CapturedText { text, range })
621
662
  }
622
663
  fn query(&self, _t: &Target, _f: PaneField) -> Result<Option<String>, TransportError> {
@@ -634,7 +675,12 @@ mod tests {
634
675
  fn list_windows(&self, _s: &SessionName) -> Result<Vec<WindowName>, TransportError> {
635
676
  unimplemented!("not reached")
636
677
  }
637
- fn set_session_env(&self, _s: &SessionName, _k: &str, _v: &str) -> Result<SetEnvOutcome, TransportError> {
678
+ fn set_session_env(
679
+ &self,
680
+ _s: &SessionName,
681
+ _k: &str,
682
+ _v: &str,
683
+ ) -> Result<SetEnvOutcome, TransportError> {
638
684
  unimplemented!("not reached")
639
685
  }
640
686
  fn kill_session(&self, _s: &SessionName) -> Result<(), TransportError> {
@@ -700,7 +746,9 @@ mod tests {
700
746
  );
701
747
  let sent = t.sent.lock().unwrap();
702
748
  assert_eq!(
703
- sent.iter().filter(|keys| keys.as_slice() == [Key::Enter]).count(),
749
+ sent.iter()
750
+ .filter(|keys| keys.as_slice() == [Key::Enter])
751
+ .count(),
704
752
  1,
705
753
  "copilot trust prompt must choose option 1 with one Enter; got {sent:?}"
706
754
  );
@@ -1130,6 +1130,34 @@ pub fn load_runtime_state(workspace: &Path) -> Result<Value, StateError> {
1130
1130
  Ok(state)
1131
1131
  }
1132
1132
 
1133
+ pub(crate) fn load_runtime_state_without_migrations(workspace: &Path) -> Result<Value, StateError> {
1134
+ let path = runtime_state_path(workspace);
1135
+ if !path.exists() {
1136
+ return Ok(
1137
+ json!({"agents": {}, "tasks": [], "session_name": null, "active_team_key": null}),
1138
+ );
1139
+ }
1140
+ let text = std::fs::read_to_string(&path)?;
1141
+ let mut state: Value = serde_json::from_str(&text)?;
1142
+ normalize_agent_session_state(&mut state);
1143
+ Ok(state)
1144
+ }
1145
+
1146
+ pub(crate) fn save_runtime_state_without_migrations(
1147
+ workspace: &Path,
1148
+ state: &Value,
1149
+ ) -> Result<(), StateError> {
1150
+ let path = runtime_state_path(workspace);
1151
+ let _lock = RuntimeLock::acquire(workspace, "state-save-raw", 2.0)?;
1152
+ if let Some(parent) = path.parent() {
1153
+ std::fs::create_dir_all(parent)?;
1154
+ }
1155
+ let payload = serde_json::to_string_pretty(state)?;
1156
+ std::fs::write(&path, payload.as_bytes())?;
1157
+ cache_set(&path, state);
1158
+ Ok(())
1159
+ }
1160
+
1133
1161
  #[cfg(test)]
1134
1162
  mod tests {
1135
1163
  #![allow(clippy::unwrap_used, clippy::panic, clippy::expect_used)]
@@ -47,6 +47,7 @@ use super::persist::{
47
47
  save_runtime_state_with_lifecycle_topology_authority_and_capture_backfill_skip as helper_write_root_with_lifecycle_topology_authority_and_capture_backfill_skip,
48
48
  save_runtime_state_with_team_tombstone_lifecycle_topology_authority as helper_write_root_with_team_tombstone_lifecycle_topology_authority,
49
49
  save_runtime_state_with_team_tombstoned_agents as helper_write_root_with_team_tombstoned_agents,
50
+ save_runtime_state_without_migrations as helper_write_root_without_migrations,
50
51
  };
51
52
  use super::projection::{
52
53
  resolve_team_scoped_state as helper_resolve_team_scoped,
@@ -173,6 +174,9 @@ pub enum StateWriteIntent<'a> {
173
174
  ClaimLeader {
174
175
  team_key: &'a str,
175
176
  },
177
+ LeaderBindingRestoreNonTargetTeams {
178
+ target_team_key: &'a str,
179
+ },
176
180
  LeaderStartBinding {
177
181
  team_key: &'a str,
178
182
  transport_kind: &'a str,
@@ -325,6 +329,9 @@ fn route_direct(
325
329
  // scoped preserve-claim-fields variant at :1702 uses the team-tombstoned
326
330
  // agents helper.
327
331
  StateWriteIntent::ClaimLeader { .. } => helper_write_root(workspace, state),
332
+ StateWriteIntent::LeaderBindingRestoreNonTargetTeams { .. } => {
333
+ helper_write_root_without_migrations(workspace, state)
334
+ }
328
335
  // LeaderStartBinding -> managed/exec/external all root-save at
329
336
  // leader/start.rs:795/903/946.
330
337
  StateWriteIntent::LeaderStartBinding { .. } => helper_write_root(workspace, state),
@@ -338,9 +345,7 @@ fn route_direct(
338
345
  // intent lives under `coordinator.abnormal_api_error_recovery`, a
339
346
  // root-scoped bookkeeping namespace that must survive across team
340
347
  // boundaries; use the same helper family as CoordinatorConptyShim.
341
- StateWriteIntent::CoordinatorApiErrorRecovery { .. } => {
342
- helper_write_root(workspace, state)
343
- }
348
+ StateWriteIntent::CoordinatorApiErrorRecovery { .. } => helper_write_root(workspace, state),
344
349
  // McpAssignTask uses the reapply variant today; direct save routes
345
350
  // to the root helper for parity.
346
351
  StateWriteIntent::McpAssignTask { .. } => helper_write_root(workspace, state),