@team-agent/installer 0.3.10 → 0.3.11

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.
@@ -89,6 +89,9 @@ pub enum TickError {
89
89
  /// messaging subsystem failure(delivery/scheduler/result watchers).
90
90
  #[error("messaging: {0}")]
91
91
  Messaging(#[from] crate::messaging::MessagingError),
92
+ /// coordinator.tick panic caught by the daemon loop.
93
+ #[error("panic: {0}")]
94
+ Panic(String),
92
95
  }
93
96
 
94
97
  // ===========================================================================
@@ -98,7 +101,8 @@ pub enum TickError {
98
101
  /// tick 末原子 save 失败注入钩(bug-084)。生产装配为 `None`(走真实 `save_runtime_state`);
99
102
  /// 测试装配一个返回 `Err` 的闭包,在不触碰真实磁盘的前提下强制 save 失败,断言 degraded
100
103
  /// `TickReport` 而非 panic/Err。porter 在 `tick` 的「ATOMIC save」包裹点先查它再落真实 save。
101
- pub type SaveHook = Box<dyn Fn(&WorkspacePath, &Value) -> Result<(), crate::state::StateError> + Send + Sync>;
104
+ pub type SaveHook =
105
+ Box<dyn Fn(&WorkspacePath, &Value) -> Result<(), crate::state::StateError> + Send + Sync>;
102
106
 
103
107
  /// tick 链式副作用 ORDER 记录器(测试探针)。porter 在 `tick` 的每个原子调用点 push 一个
104
108
  /// 稳定步骤名;测试断言固定序列。生产装配为 `None`(零开销,porter 用 `if let Some(rec)` 守卫)。
@@ -222,7 +226,9 @@ impl Coordinator {
222
226
  // become deliverable. Reset them to `accepted` so the existing
223
227
  // `deliver_pending` step below picks them up on THIS tick. Reuses the
224
228
  // delivery pipeline; no new injector. Best-effort logging on inner errors.
225
- if let Err(error) = self.requeue_trust_retries_for_handled_agents(&state, &store, &event_log) {
229
+ if let Err(error) =
230
+ self.requeue_trust_retries_for_handled_agents(&state, &store, &event_log)
231
+ {
226
232
  let _ = event_log.write(
227
233
  "messaging.trust_retry_requeue_failed",
228
234
  serde_json::json!({"error": error.to_string()}),
@@ -377,7 +383,9 @@ impl Coordinator {
377
383
  self.record_step("atomic_save");
378
384
  let saved = match &self.save_hook {
379
385
  Some(hook) => hook(&self.workspace, &state),
380
- None => crate::state::projection::save_team_scoped_state(self.workspace.as_path(), &state),
386
+ None => {
387
+ crate::state::projection::save_team_scoped_state(self.workspace.as_path(), &state)
388
+ }
381
389
  };
382
390
  if saved.is_err() {
383
391
  return Ok(base_tick_report(
@@ -390,17 +398,13 @@ impl Coordinator {
390
398
  }
391
399
 
392
400
  self.record_step("collect_results");
393
- collections.results = collect_results(
394
- crate::messaging::collect_results_and_notify_watchers(self.workspace.as_path(), &event_log)?,
395
- );
401
+ collections.results =
402
+ collect_results(crate::messaging::collect_results_and_notify_watchers(
403
+ self.workspace.as_path(),
404
+ &event_log,
405
+ )?);
396
406
  self.record_step("prune_dedupe_log");
397
- Ok(base_tick_report(
398
- true,
399
- false,
400
- None,
401
- Some(true),
402
- collections,
403
- ))
407
+ Ok(base_tick_report(true, false, None, Some(true), collections))
404
408
  }
405
409
 
406
410
  // #236 nag_removal (N35): the framework-synthesized idle/stuck/deadlock nag
@@ -408,7 +412,11 @@ impl Coordinator {
408
412
  // were removed by design. Delivery primitives still flow through the rest of
409
413
  // the tick body unchanged.
410
414
 
411
- fn capture_missing_sessions(&self, state: &mut Value, event_log: &EventLog) -> Result<(), TickError> {
415
+ fn capture_missing_sessions(
416
+ &self,
417
+ state: &mut Value,
418
+ event_log: &EventLog,
419
+ ) -> Result<(), TickError> {
412
420
  let report = crate::session_capture::capture_missing_provider_sessions_once(
413
421
  state,
414
422
  &mut |provider| self.provider_registry.adapter_for(provider),
@@ -438,7 +446,10 @@ impl Coordinator {
438
446
  let snapshot = state.clone();
439
447
  let team = crate::state::projection::team_state_key(&snapshot);
440
448
  let team_key = Some(crate::model::ids::TeamKey::new(team.clone()));
441
- let session_name = state.get("session_name").and_then(Value::as_str).map(str::to_string);
449
+ let session_name = state
450
+ .get("session_name")
451
+ .and_then(Value::as_str)
452
+ .map(str::to_string);
442
453
  // B-4 / 036b N36 三路可用 — sync_health 内 per-agent capture 失败本就降级
443
454
  // (写 coordinator.agent_capture_failed 后 continue),不打断 deliver_pending
444
455
  // 主干。但 contract 要求一条【tick 级】可观测的 step-failed 信号 —
@@ -447,13 +458,17 @@ impl Coordinator {
447
458
  let mut had_capture_failure = false;
448
459
  // P5 (C-P5-2): one list-windows per SESSION per tick — memoized across the
449
460
  // agent loop instead of one fork per agent.
450
- let mut windows_by_session: BTreeMap<String, Result<Vec<crate::transport::WindowName>, String>> =
451
- BTreeMap::new();
461
+ let mut windows_by_session: BTreeMap<
462
+ String,
463
+ Result<Vec<crate::transport::WindowName>, String>,
464
+ > = BTreeMap::new();
452
465
  let Some(agents) = state.get_mut("agents").and_then(Value::as_object_mut) else {
453
466
  return Ok(captures);
454
467
  };
455
468
  for (agent_id, agent) in agents {
456
- let Some((session, window, target)) = capture_window_target(agent, session_name.as_deref()) else {
469
+ let Some((session, window, target)) =
470
+ capture_window_target(agent, session_name.as_deref())
471
+ else {
457
472
  continue;
458
473
  };
459
474
  let windows = match windows_by_session
@@ -535,7 +550,14 @@ impl Coordinator {
535
550
  );
536
551
  write_activity(agent, &activity, false);
537
552
  let last_output_at = last_output_at_now;
538
- write_agent_health(store, &team, agent_id, agent, &activity, last_output_at.as_deref())?;
553
+ write_agent_health(
554
+ store,
555
+ &team,
556
+ agent_id,
557
+ agent,
558
+ &activity,
559
+ last_output_at.as_deref(),
560
+ )?;
539
561
  let pane_info = matching_capture_pane_info(agent, &session, &window, pane_infos);
540
562
  let pane_id = pane_info
541
563
  .as_ref()
@@ -547,7 +569,10 @@ impl Coordinator {
547
569
  CapturedRuntimeFact {
548
570
  team_key: team_key.clone(),
549
571
  agent_id: AgentId::new(agent_id.clone()),
550
- provider: agent.get("provider").and_then(Value::as_str).and_then(parse_provider),
572
+ provider: agent
573
+ .get("provider")
574
+ .and_then(Value::as_str)
575
+ .and_then(parse_provider),
551
576
  session_name: Some(session),
552
577
  window: Some(window),
553
578
  pane_id,
@@ -620,14 +645,22 @@ impl Coordinator {
620
645
  let team = crate::state::projection::team_state_key(&snapshot);
621
646
  let session_name = snapshot.get("session_name").and_then(Value::as_str);
622
647
  for agent in abnormal_watch_agents(&snapshot) {
623
- let rollout_path = resolve_agent_rollout_path(self.workspace.as_path(), &agent.rollout_path);
648
+ let rollout_path =
649
+ resolve_agent_rollout_path(self.workspace.as_path(), &agent.rollout_path);
624
650
  let metadata = match std::fs::metadata(&rollout_path) {
625
651
  Ok(metadata) => metadata,
626
652
  Err(error) => {
627
653
  upsert_abnormal_watch(
628
654
  state,
629
655
  &agent.agent_id,
630
- abnormal_watch_payload(&agent, None, None, "unverifiable", None, Some(error.to_string())),
656
+ abnormal_watch_payload(
657
+ &agent,
658
+ None,
659
+ None,
660
+ "unverifiable",
661
+ None,
662
+ Some(error.to_string()),
663
+ ),
631
664
  );
632
665
  continue;
633
666
  }
@@ -638,9 +671,10 @@ impl Coordinator {
638
671
  // read at all (live sample: 332MB whole-file read per agent per 2s tick).
639
672
  // ANY field change (including a size shrink / truncate) falls through to the
640
673
  // re-read below.
641
- if let (Some(mtime), Some(stored)) =
642
- (mtime_ns, abnormal_watch_stored_metadata(&snapshot, &agent.agent_id))
643
- {
674
+ if let (Some(mtime), Some(stored)) = (
675
+ mtime_ns,
676
+ abnormal_watch_stored_metadata(&snapshot, &agent.agent_id),
677
+ ) {
644
678
  if stored == (size, mtime) {
645
679
  continue;
646
680
  }
@@ -654,17 +688,20 @@ impl Coordinator {
654
688
  upsert_abnormal_watch(
655
689
  state,
656
690
  &agent.agent_id,
657
- abnormal_watch_payload(&agent, Some(size), mtime_ns, "unverifiable", None, Some(error.to_string())),
691
+ abnormal_watch_payload(
692
+ &agent,
693
+ Some(size),
694
+ mtime_ns,
695
+ "unverifiable",
696
+ None,
697
+ Some(error.to_string()),
698
+ ),
658
699
  );
659
700
  continue;
660
701
  }
661
702
  };
662
- let liveness = agent_process_liveness(
663
- &agent,
664
- session_name,
665
- targets,
666
- self.transport.as_ref(),
667
- );
703
+ let liveness =
704
+ agent_process_liveness(&agent, session_name, targets, self.transport.as_ref());
668
705
  let fact = crate::provider::latest_explicit_error_fact(agent.provider, &text);
669
706
  let decision = abnormal_exit_decision(liveness.state, fact.as_ref());
670
707
  let check_key = abnormal_check_key(&agent, &liveness, fact.as_ref(), size);
@@ -680,8 +717,19 @@ impl Coordinator {
680
717
  None,
681
718
  ),
682
719
  );
683
- if abnormal_last_check_key(state, &agent.agent_id).as_deref() != Some(check_key.as_str()) {
684
- write_abnormal_check(event_log, &team, &agent, &liveness, fact.as_ref(), decision, size, mtime_ns)?;
720
+ if abnormal_last_check_key(state, &agent.agent_id).as_deref()
721
+ != Some(check_key.as_str())
722
+ {
723
+ write_abnormal_check(
724
+ event_log,
725
+ &team,
726
+ &agent,
727
+ &liveness,
728
+ fact.as_ref(),
729
+ decision,
730
+ size,
731
+ mtime_ns,
732
+ )?;
685
733
  mark_abnormal_checked(state, &agent.agent_id, &check_key);
686
734
  }
687
735
  let fact = match (decision, fact) {
@@ -700,7 +748,9 @@ impl Coordinator {
700
748
  (AbnormalExitDecision::Notify, None) => continue,
701
749
  };
702
750
  let dedupe_key = abnormal_dedupe_key(&agent, &fact, size);
703
- if abnormal_last_notified_key(state, &agent.agent_id).as_deref() == Some(dedupe_key.as_str()) {
751
+ if abnormal_last_notified_key(state, &agent.agent_id).as_deref()
752
+ == Some(dedupe_key.as_str())
753
+ {
704
754
  continue;
705
755
  }
706
756
  let content = format_abnormal_exit_message(&team, &agent, &fact, &liveness, size);
@@ -755,7 +805,10 @@ impl Coordinator {
755
805
  }
756
806
 
757
807
  fn handle_startup_prompts(&self, state: &mut Value, event_log: &EventLog) {
758
- let session_name = state.get("session_name").and_then(Value::as_str).map(str::to_string);
808
+ let session_name = state
809
+ .get("session_name")
810
+ .and_then(Value::as_str)
811
+ .map(str::to_string);
759
812
  let Some(agents) = state.get_mut("agents").and_then(Value::as_object_mut) else {
760
813
  return;
761
814
  };
@@ -827,7 +880,10 @@ impl Coordinator {
827
880
  continue;
828
881
  };
829
882
  agent_obj.insert("startup_prompts".to_string(), serde_json::json!("handled"));
830
- agent_obj.insert("startup_prompt_status".to_string(), serde_json::json!("handled"));
883
+ agent_obj.insert(
884
+ "startup_prompt_status".to_string(),
885
+ serde_json::json!("handled"),
886
+ );
831
887
  agent_obj.insert("startup_prompt_handled".to_string(), handled_payload);
832
888
  }
833
889
  }
@@ -891,7 +947,10 @@ impl Coordinator {
891
947
  ) -> Result<(), TickError> {
892
948
  let snapshot = state.clone();
893
949
  let team = crate::state::projection::team_state_key(&snapshot);
894
- let session_name = snapshot.get("session_name").and_then(Value::as_str).map(str::to_string);
950
+ let session_name = snapshot
951
+ .get("session_name")
952
+ .and_then(Value::as_str)
953
+ .map(str::to_string);
895
954
  let mut dedup_updates = Vec::new();
896
955
  {
897
956
  let Some(agents) = state.get_mut("agents").and_then(Value::as_object_mut) else {
@@ -942,32 +1001,32 @@ impl Coordinator {
942
1001
  });
943
1002
  let choice = choose_internal_mcp_approval_choice(&prompt);
944
1003
  let keys = approval_choice_keys(&prompt, &captured.text, &choice)
945
- .into_iter()
946
- .filter_map(runtime_approval_key)
947
- .collect::<Vec<_>>();
948
- // A-6 / Python approvals/runtime_prompts.py:21-43: prompts are handled
949
- // per-agent with run_cmd(check=False) — one agent's tmux failure must
950
- // not abort the whole tick for the rest.
951
- if let Err(error) = self.transport.send_keys(&target, &keys) {
952
- event_log.write(
953
- "runtime_approval.send_keys_failed",
954
- serde_json::json!({
955
- "agent_id": agent_id,
956
- "target": format!("{target:?}"),
957
- "tool": prompt.tool,
958
- "error": error.to_string(),
959
- }),
960
- )?;
961
- continue;
962
- }
963
- let after = self
964
- .transport
965
- .capture(&target, crate::transport::CaptureRange::Tail(80))
966
- .ok()
967
- .and_then(|capture| extract_approval_prompt(agent_id, &capture.text));
968
- let cleared = after
969
- .as_ref()
970
- .is_none_or(|after| after.prompt != prompt.prompt || after.tool != prompt.tool);
1004
+ .into_iter()
1005
+ .filter_map(runtime_approval_key)
1006
+ .collect::<Vec<_>>();
1007
+ // A-6 / Python approvals/runtime_prompts.py:21-43: prompts are handled
1008
+ // per-agent with run_cmd(check=False) — one agent's tmux failure must
1009
+ // not abort the whole tick for the rest.
1010
+ if let Err(error) = self.transport.send_keys(&target, &keys) {
1011
+ event_log.write(
1012
+ "runtime_approval.send_keys_failed",
1013
+ serde_json::json!({
1014
+ "agent_id": agent_id,
1015
+ "target": format!("{target:?}"),
1016
+ "tool": prompt.tool,
1017
+ "error": error.to_string(),
1018
+ }),
1019
+ )?;
1020
+ continue;
1021
+ }
1022
+ let after = self
1023
+ .transport
1024
+ .capture(&target, crate::transport::CaptureRange::Tail(80))
1025
+ .ok()
1026
+ .and_then(|capture| extract_approval_prompt(agent_id, &capture.text));
1027
+ let cleared = after.as_ref().is_none_or(|after| {
1028
+ after.prompt != prompt.prompt || after.tool != prompt.tool
1029
+ });
971
1030
  event_log.write(
972
1031
  "runtime_approval.auto_approved",
973
1032
  serde_json::json!({
@@ -1001,14 +1060,16 @@ impl Coordinator {
1001
1060
  )?;
1002
1061
  }
1003
1062
  RuntimeApprovalDecision::AwaitingHumanConfirm => {
1004
- let Some(reason) = awaiting_human_confirm_reason(&prompt, auto_answer_allowed) else {
1063
+ let Some(reason) =
1064
+ awaiting_human_confirm_reason(&prompt, auto_answer_allowed)
1065
+ else {
1005
1066
  continue;
1006
1067
  };
1007
1068
  let fact = awaiting_human_confirm_fact(&team, agent_id, &prompt, reason);
1008
1069
  let previous = agent
1009
- .get("awaiting_human_confirm")
1010
- .and_then(|v| v.get("fingerprint"))
1011
- .and_then(Value::as_str);
1070
+ .get("awaiting_human_confirm")
1071
+ .and_then(|v| v.get("fingerprint"))
1072
+ .and_then(Value::as_str);
1012
1073
  if previous == Some(fact.fingerprint.as_str())
1013
1074
  || state_awaiting_human_confirm_fingerprint(&snapshot, &team, agent_id)
1014
1075
  .as_deref()
@@ -1020,10 +1081,10 @@ impl Coordinator {
1020
1081
  let notification = awaiting_human_confirm_payload(agent, &fact);
1021
1082
  let content = notification.to_string();
1022
1083
  let _ = crate::messaging::send_to_leader_receiver(
1023
- self.workspace.as_path(),
1024
- &snapshot,
1025
- "leader",
1026
- &content,
1084
+ self.workspace.as_path(),
1085
+ &snapshot,
1086
+ "leader",
1087
+ &content,
1027
1088
  None,
1028
1089
  agent_id,
1029
1090
  false,
@@ -1034,43 +1095,43 @@ impl Coordinator {
1034
1095
  remember_awaiting_human_confirm(agent, &fact);
1035
1096
  dedup_updates.push(AwaitingDedupUpdate::Remember(fact.clone()));
1036
1097
  match reason {
1037
- "tool_not_allowlisted" => {
1038
- event_log.write(
1039
- "runtime_approval.tool_not_allowlisted",
1040
- serde_json::json!({
1041
- "agent_id": agent_id,
1042
- "tool": prompt.tool,
1043
- "kind": prompt.kind,
1044
- "prompt": prompt.prompt,
1045
- }),
1046
- )?;
1098
+ "tool_not_allowlisted" => {
1099
+ event_log.write(
1100
+ "runtime_approval.tool_not_allowlisted",
1101
+ serde_json::json!({
1102
+ "agent_id": agent_id,
1103
+ "tool": prompt.tool,
1104
+ "kind": prompt.kind,
1105
+ "prompt": prompt.prompt,
1106
+ }),
1107
+ )?;
1108
+ }
1109
+ "leader_restricted" | "leader_safety_restricted" => {
1110
+ event_log.write(
1111
+ "runtime_approval.blocked_by_leader_safety",
1112
+ serde_json::json!({
1113
+ "agent_id": agent_id,
1114
+ "tool": prompt.tool,
1115
+ "command": prompt.command,
1116
+ "kind": prompt.kind,
1117
+ "prompt": prompt.prompt,
1118
+ }),
1119
+ )?;
1120
+ }
1121
+ "command_approval_requires_human" => {
1122
+ event_log.write(
1123
+ "runtime_approval.command_approval_requires_human",
1124
+ serde_json::json!({
1125
+ "agent_id": agent_id,
1126
+ "tool": prompt.tool,
1127
+ "command": prompt.command,
1128
+ "kind": prompt.kind,
1129
+ "prompt": prompt.prompt,
1130
+ }),
1131
+ )?;
1132
+ }
1133
+ _ => {}
1047
1134
  }
1048
- "leader_restricted" | "leader_safety_restricted" => {
1049
- event_log.write(
1050
- "runtime_approval.blocked_by_leader_safety",
1051
- serde_json::json!({
1052
- "agent_id": agent_id,
1053
- "tool": prompt.tool,
1054
- "command": prompt.command,
1055
- "kind": prompt.kind,
1056
- "prompt": prompt.prompt,
1057
- }),
1058
- )?;
1059
- }
1060
- "command_approval_requires_human" => {
1061
- event_log.write(
1062
- "runtime_approval.command_approval_requires_human",
1063
- serde_json::json!({
1064
- "agent_id": agent_id,
1065
- "tool": prompt.tool,
1066
- "command": prompt.command,
1067
- "kind": prompt.kind,
1068
- "prompt": prompt.prompt,
1069
- }),
1070
- )?;
1071
- }
1072
- _ => {}
1073
- }
1074
1135
  }
1075
1136
  RuntimeApprovalDecision::Ignore => {
1076
1137
  clear_awaiting_human_confirm(agent);
@@ -1084,7 +1145,9 @@ impl Coordinator {
1084
1145
  }
1085
1146
  for update in dedup_updates {
1086
1147
  match update {
1087
- AwaitingDedupUpdate::Remember(fact) => remember_state_awaiting_human_confirm(state, &fact),
1148
+ AwaitingDedupUpdate::Remember(fact) => {
1149
+ remember_state_awaiting_human_confirm(state, &fact)
1150
+ }
1088
1151
  AwaitingDedupUpdate::Clear { team, agent_id } => {
1089
1152
  clear_state_awaiting_human_confirm(state, &team, &agent_id)
1090
1153
  }
@@ -1126,7 +1189,9 @@ impl Coordinator {
1126
1189
  /// Python 是 `python -m team_agent.coordinator`,`lifecycle.py:108`)。
1127
1190
  /// **schema 兼容门**:三元任一不匹配 → restart_incompatible,**不可静默继续**(card §89)。
1128
1191
  pub fn start(&self) -> Result<StartReport, StartError> {
1129
- let health = self.health().map_err(|e| std::io::Error::other(e.to_string()))?;
1192
+ let health = self
1193
+ .health()
1194
+ .map_err(|e| std::io::Error::other(e.to_string()))?;
1130
1195
  if health.ok {
1131
1196
  return Ok(StartReport {
1132
1197
  ok: true,
@@ -1164,14 +1229,26 @@ impl Coordinator {
1164
1229
  pub fn stop(&self) -> Result<StopReport, StopError> {
1165
1230
  let pid_path = coordinator_pid_path(&self.workspace);
1166
1231
  if !pid_path.exists() {
1167
- return Ok(StopReport { ok: true, status: StopOutcome::Missing, pid: None });
1232
+ return Ok(StopReport {
1233
+ ok: true,
1234
+ status: StopOutcome::Missing,
1235
+ pid: None,
1236
+ });
1168
1237
  }
1169
1238
  let pid = read_pid_file(&pid_path);
1170
1239
  remove_file_if_exists(&pid_path)?;
1171
1240
  remove_file_if_exists(&coordinator_meta_path(&self.workspace))?;
1172
1241
  match pid {
1173
- Some(pid) => Ok(StopReport { ok: true, status: StopOutcome::Stopped, pid: Some(pid) }),
1174
- None => Ok(StopReport { ok: true, status: StopOutcome::InvalidPidRemoved, pid: None }),
1242
+ Some(pid) => Ok(StopReport {
1243
+ ok: true,
1244
+ status: StopOutcome::Stopped,
1245
+ pid: Some(pid),
1246
+ }),
1247
+ None => Ok(StopReport {
1248
+ ok: true,
1249
+ status: StopOutcome::InvalidPidRemoved,
1250
+ pid: None,
1251
+ }),
1175
1252
  }
1176
1253
  }
1177
1254
 
@@ -1236,20 +1313,16 @@ fn empty_tick_report(
1236
1313
  reason: Option<TickStopReason>,
1237
1314
  persisted: Option<bool>,
1238
1315
  ) -> TickReport {
1239
- base_tick_report(
1240
- ok,
1241
- stop,
1242
- reason,
1243
- persisted,
1244
- TickCollections::default(),
1245
- )
1316
+ base_tick_report(ok, stop, reason, persisted, TickCollections::default())
1246
1317
  }
1247
1318
 
1248
1319
  fn collect_results(value: Value) -> Vec<CollectedResult> {
1249
1320
  let Some(result_id) = value.get("result_id").and_then(Value::as_str) else {
1250
1321
  return Vec::new();
1251
1322
  };
1252
- vec![CollectedResult { result_id: result_id.to_string() }]
1323
+ vec![CollectedResult {
1324
+ result_id: result_id.to_string(),
1325
+ }]
1253
1326
  }
1254
1327
 
1255
1328
  struct ProviderTurnClassifier;
@@ -1282,8 +1355,7 @@ impl TurnStateClassifier for ProviderTurnClassifier {
1282
1355
  /// `coordinator.coordinator_tick_iteration_count` load fine (read-compat, C-P3-3) —
1283
1356
  /// new versions simply stop writing it.
1284
1357
  fn increment_coordinator_tick_iteration_count(workspace: &WorkspacePath) {
1285
- let path =
1286
- crate::model::paths::runtime_dir(workspace.as_path()).join("coordinator_tick.json");
1358
+ let path = crate::model::paths::runtime_dir(workspace.as_path()).join("coordinator_tick.json");
1287
1359
  let next = std::fs::read_to_string(&path)
1288
1360
  .ok()
1289
1361
  .and_then(|text| serde_json::from_str::<Value>(&text).ok())
@@ -1423,13 +1495,13 @@ fn abnormal_watch_agents(state: &Value) -> Vec<AbnormalWatchAgent> {
1423
1495
  agents
1424
1496
  .iter()
1425
1497
  .filter_map(|(agent_id, agent)| {
1426
- if matches!(
1427
- agent.get("status").and_then(Value::as_str),
1428
- Some("paused")
1429
- ) {
1498
+ if matches!(agent.get("status").and_then(Value::as_str), Some("paused")) {
1430
1499
  return None;
1431
1500
  }
1432
- let provider = agent.get("provider").and_then(Value::as_str).and_then(parse_provider)?;
1501
+ let provider = agent
1502
+ .get("provider")
1503
+ .and_then(Value::as_str)
1504
+ .and_then(parse_provider)?;
1433
1505
  let rollout_path_display = ["rollout_path", "transcript_path", "session_log_path"]
1434
1506
  .into_iter()
1435
1507
  .find_map(|key| agent.get(key).and_then(Value::as_str))
@@ -1440,10 +1512,19 @@ fn abnormal_watch_agents(state: &Value) -> Vec<AbnormalWatchAgent> {
1440
1512
  provider,
1441
1513
  rollout_path: PathBuf::from(&rollout_path_display),
1442
1514
  rollout_path_display,
1443
- status: agent.get("status").and_then(Value::as_str).map(str::to_string),
1515
+ status: agent
1516
+ .get("status")
1517
+ .and_then(Value::as_str)
1518
+ .map(str::to_string),
1444
1519
  process_liveness: explicit_process_liveness(agent),
1445
- window: agent.get("window").and_then(Value::as_str).map(str::to_string),
1446
- pane_id: agent.get("pane_id").and_then(Value::as_str).map(str::to_string),
1520
+ window: agent
1521
+ .get("window")
1522
+ .and_then(Value::as_str)
1523
+ .map(str::to_string),
1524
+ pane_id: agent
1525
+ .get("pane_id")
1526
+ .and_then(Value::as_str)
1527
+ .map(str::to_string),
1447
1528
  pid: agent_pid(agent),
1448
1529
  current_command: agent
1449
1530
  .get("pane_current_command")
@@ -1462,12 +1543,19 @@ fn agent_pid(agent: &Value) -> Option<Pid> {
1462
1543
  }
1463
1544
 
1464
1545
  fn explicit_process_liveness(agent: &Value) -> Option<ProcessLiveness> {
1465
- if let Some(process) = agent.get("provider_process").or_else(|| agent.get("process")) {
1546
+ if let Some(process) = agent
1547
+ .get("provider_process")
1548
+ .or_else(|| agent.get("process"))
1549
+ {
1466
1550
  if let Some(liveness) = explicit_process_liveness(process) {
1467
1551
  return Some(liveness);
1468
1552
  }
1469
1553
  }
1470
- for key in ["provider_process_liveness", "process_liveness", "pane_liveness"] {
1554
+ for key in [
1555
+ "provider_process_liveness",
1556
+ "process_liveness",
1557
+ "pane_liveness",
1558
+ ] {
1471
1559
  match agent.get(key).and_then(Value::as_str) {
1472
1560
  Some("dead") => return Some(ProcessLiveness::Dead),
1473
1561
  Some("alive" | "live") => return Some(ProcessLiveness::Alive),
@@ -1475,14 +1563,32 @@ fn explicit_process_liveness(agent: &Value) -> Option<ProcessLiveness> {
1475
1563
  _ => {}
1476
1564
  }
1477
1565
  }
1478
- for key in ["provider_process_alive", "process_alive", "provider_alive", "alive"] {
1566
+ for key in [
1567
+ "provider_process_alive",
1568
+ "process_alive",
1569
+ "provider_alive",
1570
+ "alive",
1571
+ ] {
1479
1572
  if let Some(alive) = agent.get(key).and_then(Value::as_bool) {
1480
- return Some(if alive { ProcessLiveness::Alive } else { ProcessLiveness::Dead });
1573
+ return Some(if alive {
1574
+ ProcessLiveness::Alive
1575
+ } else {
1576
+ ProcessLiveness::Dead
1577
+ });
1481
1578
  }
1482
1579
  }
1483
- for key in ["provider_process_dead", "process_dead", "provider_dead", "dead"] {
1580
+ for key in [
1581
+ "provider_process_dead",
1582
+ "process_dead",
1583
+ "provider_dead",
1584
+ "dead",
1585
+ ] {
1484
1586
  if let Some(dead) = agent.get(key).and_then(Value::as_bool) {
1485
- return Some(if dead { ProcessLiveness::Dead } else { ProcessLiveness::Alive });
1587
+ return Some(if dead {
1588
+ ProcessLiveness::Dead
1589
+ } else {
1590
+ ProcessLiveness::Alive
1591
+ });
1486
1592
  }
1487
1593
  }
1488
1594
  for key in ["status", "state", "liveness"] {
@@ -1500,7 +1606,10 @@ fn explicit_process_liveness(agent: &Value) -> Option<ProcessLiveness> {
1500
1606
 
1501
1607
  fn json_u32(value: Option<&Value>) -> Option<u32> {
1502
1608
  value
1503
- .and_then(|v| v.as_u64().or_else(|| v.as_i64().and_then(|n| u64::try_from(n).ok())))
1609
+ .and_then(|v| {
1610
+ v.as_u64()
1611
+ .or_else(|| v.as_i64().and_then(|n| u64::try_from(n).ok()))
1612
+ })
1504
1613
  .and_then(|n| u32::try_from(n).ok())
1505
1614
  }
1506
1615
 
@@ -1514,15 +1623,17 @@ fn agent_process_liveness(
1514
1623
  return pid_process_check("pid", pid);
1515
1624
  }
1516
1625
  if let Some(liveness) = agent.process_liveness {
1517
- return process_check(liveness, format!("explicit:{}", process_liveness_wire(liveness)));
1626
+ return process_check(
1627
+ liveness,
1628
+ format!("explicit:{}", process_liveness_wire(liveness)),
1629
+ );
1518
1630
  }
1519
1631
  if agent.status.as_deref().is_some_and(|status| {
1520
1632
  matches!(
1521
1633
  status,
1522
1634
  "stopped" | "missing" | "error" | "dead" | "exited" | "terminated" | "crashed"
1523
1635
  )
1524
- })
1525
- {
1636
+ }) {
1526
1637
  return process_check(
1527
1638
  ProcessLiveness::Dead,
1528
1639
  format!("status:{}", agent.status.as_deref().unwrap_or("unknown")),
@@ -1538,7 +1649,10 @@ fn agent_process_liveness(
1538
1649
  if let Some(pid) = target.pane_pid.map(Pid::new) {
1539
1650
  return pid_process_check("pane_pid", pid);
1540
1651
  }
1541
- return process_check(ProcessLiveness::Unverifiable, "pane_present_pid_unknown".to_string());
1652
+ return process_check(
1653
+ ProcessLiveness::Unverifiable,
1654
+ "pane_present_pid_unknown".to_string(),
1655
+ );
1542
1656
  }
1543
1657
  if let Some(pane_id) = agent.pane_id.as_deref() {
1544
1658
  let pane = crate::transport::PaneId::new(pane_id);
@@ -1546,27 +1660,37 @@ fn agent_process_liveness(
1546
1660
  Ok(crate::transport::PaneLiveness::Dead) => {
1547
1661
  process_check(ProcessLiveness::Dead, format!("pane_dead:{pane_id}"))
1548
1662
  }
1549
- Ok(crate::transport::PaneLiveness::Live) => {
1550
- process_check(ProcessLiveness::Unverifiable, format!("pane_live_pid_unknown:{pane_id}"))
1551
- }
1552
- Ok(crate::transport::PaneLiveness::Unknown) => {
1553
- process_check(ProcessLiveness::Unverifiable, format!("pane_unknown:{pane_id}"))
1554
- }
1555
- Err(error) => {
1556
- process_check(ProcessLiveness::Unverifiable, format!("pane_unverifiable:{pane_id}:{error}"))
1557
- }
1663
+ Ok(crate::transport::PaneLiveness::Live) => process_check(
1664
+ ProcessLiveness::Unverifiable,
1665
+ format!("pane_live_pid_unknown:{pane_id}"),
1666
+ ),
1667
+ Ok(crate::transport::PaneLiveness::Unknown) => process_check(
1668
+ ProcessLiveness::Unverifiable,
1669
+ format!("pane_unknown:{pane_id}"),
1670
+ ),
1671
+ Err(error) => process_check(
1672
+ ProcessLiveness::Unverifiable,
1673
+ format!("pane_unverifiable:{pane_id}:{error}"),
1674
+ ),
1558
1675
  };
1559
1676
  }
1560
1677
  let (Some(session), Some(window)) = (session_name, agent.window.as_deref()) else {
1561
- return process_check(ProcessLiveness::Unverifiable, "missing_session_or_window".to_string());
1678
+ return process_check(
1679
+ ProcessLiveness::Unverifiable,
1680
+ "missing_session_or_window".to_string(),
1681
+ );
1562
1682
  };
1563
1683
  let session = crate::transport::SessionName::new(session);
1564
1684
  match transport.list_windows(&session) {
1565
- Ok(windows) if windows.iter().any(|known| known.as_str() == window) => {
1566
- process_check(ProcessLiveness::Unverifiable, "window_present_pid_unknown".to_string())
1567
- }
1685
+ Ok(windows) if windows.iter().any(|known| known.as_str() == window) => process_check(
1686
+ ProcessLiveness::Unverifiable,
1687
+ "window_present_pid_unknown".to_string(),
1688
+ ),
1568
1689
  Ok(_) => process_check(ProcessLiveness::Dead, format!("window_missing:{window}")),
1569
- Err(error) => process_check(ProcessLiveness::Unverifiable, format!("window_unverifiable:{window}:{error}")),
1690
+ Err(error) => process_check(
1691
+ ProcessLiveness::Unverifiable,
1692
+ format!("window_unverifiable:{window}:{error}"),
1693
+ ),
1570
1694
  }
1571
1695
  }
1572
1696
 
@@ -1576,7 +1700,10 @@ fn matching_agent_target<'a>(
1576
1700
  targets: &'a [crate::transport::PaneInfo],
1577
1701
  ) -> Option<&'a crate::transport::PaneInfo> {
1578
1702
  if let Some(pane_id) = agent.pane_id.as_deref() {
1579
- if let Some(target) = targets.iter().find(|target| target.pane_id.as_str() == pane_id) {
1703
+ if let Some(target) = targets
1704
+ .iter()
1705
+ .find(|target| target.pane_id.as_str() == pane_id)
1706
+ {
1580
1707
  return Some(target);
1581
1708
  }
1582
1709
  }
@@ -1596,7 +1723,10 @@ fn pid_process_check(label: &str, pid: Pid) -> ProcessCheck {
1596
1723
  match pid_is_running(pid) {
1597
1724
  Ok(true) => process_check(ProcessLiveness::Alive, format!("{label}_running:{pid}")),
1598
1725
  Ok(false) => process_check(ProcessLiveness::Dead, format!("{label}_not_running:{pid}")),
1599
- Err(error) => process_check(ProcessLiveness::Unverifiable, format!("{label}_unverifiable:{pid}:{error}")),
1726
+ Err(error) => process_check(
1727
+ ProcessLiveness::Unverifiable,
1728
+ format!("{label}_unverifiable:{pid}:{error}"),
1729
+ ),
1600
1730
  }
1601
1731
  }
1602
1732
 
@@ -1604,7 +1734,10 @@ fn command_process_check(provider: crate::model::enums::Provider, command: &str)
1604
1734
  if provider_command_matches(provider, command) {
1605
1735
  process_check(ProcessLiveness::Alive, format!("current_command:{command}"))
1606
1736
  } else {
1607
- process_check(ProcessLiveness::Dead, format!("provider_not_foreground:{command}"))
1737
+ process_check(
1738
+ ProcessLiveness::Dead,
1739
+ format!("provider_not_foreground:{command}"),
1740
+ )
1608
1741
  }
1609
1742
  }
1610
1743
 
@@ -1803,7 +1936,10 @@ fn mark_abnormal_notified(state: &mut Value, agent_id: &str, key: &str) {
1803
1936
  }
1804
1937
  if let Some(obj) = entry.as_object_mut() {
1805
1938
  obj.insert("last_notified_key".to_string(), serde_json::json!(key));
1806
- obj.insert("last_notified_at".to_string(), serde_json::json!(chrono::Utc::now().to_rfc3339()));
1939
+ obj.insert(
1940
+ "last_notified_at".to_string(),
1941
+ serde_json::json!(chrono::Utc::now().to_rfc3339()),
1942
+ );
1807
1943
  }
1808
1944
  }
1809
1945
  }
@@ -1818,7 +1954,10 @@ fn mark_abnormal_suppressed(state: &mut Value, agent_id: &str, key: &str) {
1818
1954
  }
1819
1955
  if let Some(obj) = entry.as_object_mut() {
1820
1956
  obj.insert("last_suppressed_key".to_string(), serde_json::json!(key));
1821
- obj.insert("last_suppressed_at".to_string(), serde_json::json!(chrono::Utc::now().to_rfc3339()));
1957
+ obj.insert(
1958
+ "last_suppressed_at".to_string(),
1959
+ serde_json::json!(chrono::Utc::now().to_rfc3339()),
1960
+ );
1822
1961
  }
1823
1962
  }
1824
1963
  }
@@ -1855,7 +1994,10 @@ fn mark_abnormal_checked(state: &mut Value, agent_id: &str, key: &str) {
1855
1994
  }
1856
1995
  if let Some(obj) = entry.as_object_mut() {
1857
1996
  obj.insert("last_check_key".to_string(), serde_json::json!(key));
1858
- obj.insert("last_check_at".to_string(), serde_json::json!(chrono::Utc::now().to_rfc3339()));
1997
+ obj.insert(
1998
+ "last_check_at".to_string(),
1999
+ serde_json::json!(chrono::Utc::now().to_rfc3339()),
2000
+ );
1859
2001
  }
1860
2002
  }
1861
2003
  }
@@ -2043,7 +2185,10 @@ fn capture_window_target(
2043
2185
  crate::transport::WindowName,
2044
2186
  crate::transport::Target,
2045
2187
  )> {
2046
- let window = agent.get("window").and_then(Value::as_str).filter(|s| !s.is_empty())?;
2188
+ let window = agent
2189
+ .get("window")
2190
+ .and_then(Value::as_str)
2191
+ .filter(|s| !s.is_empty())?;
2047
2192
  let session = session_name.filter(|s| !s.is_empty())?;
2048
2193
  let session = crate::transport::SessionName::new(session);
2049
2194
  let window = crate::transport::WindowName::new(window);
@@ -2093,13 +2238,18 @@ fn agent_rollout_path(agent: &Value) -> Option<PathBuf> {
2093
2238
  .map(PathBuf::from)
2094
2239
  }
2095
2240
 
2096
- fn runtime_approval_target(agent: &Value, session_name: Option<&str>) -> Option<crate::transport::Target> {
2241
+ fn runtime_approval_target(
2242
+ agent: &Value,
2243
+ session_name: Option<&str>,
2244
+ ) -> Option<crate::transport::Target> {
2097
2245
  if let Some(pane_id) = agent
2098
2246
  .get("pane_id")
2099
2247
  .and_then(Value::as_str)
2100
2248
  .filter(|pane_id| !pane_id.is_empty())
2101
2249
  {
2102
- return Some(crate::transport::Target::Pane(crate::transport::PaneId::new(pane_id)));
2250
+ return Some(crate::transport::Target::Pane(
2251
+ crate::transport::PaneId::new(pane_id),
2252
+ ));
2103
2253
  }
2104
2254
  capture_window_target(agent, session_name).map(|(_, _, target)| target)
2105
2255
  }
@@ -2200,7 +2350,14 @@ fn awaiting_human_confirm_payload(
2200
2350
  fact: &crate::provider::AwaitingHumanConfirmFact,
2201
2351
  ) -> Value {
2202
2352
  let mut payload = fact.to_event_payload();
2203
- let excerpt = fact.prompt.lines().next().unwrap_or("").chars().take(240).collect::<String>();
2353
+ let excerpt = fact
2354
+ .prompt
2355
+ .lines()
2356
+ .next()
2357
+ .unwrap_or("")
2358
+ .chars()
2359
+ .take(240)
2360
+ .collect::<String>();
2204
2361
  if let Some(obj) = payload.as_object_mut() {
2205
2362
  obj.insert("team_id".to_string(), serde_json::json!(fact.team));
2206
2363
  obj.insert("owner_team_id".to_string(), serde_json::json!(fact.team));
@@ -2363,7 +2520,10 @@ fn write_activity(
2363
2520
  activity: &crate::messaging::AgentActivity,
2364
2521
  output_advanced: bool,
2365
2522
  ) -> Option<String> {
2366
- let previous_last_output = agent.get("last_output_at").and_then(Value::as_str).map(str::to_string);
2523
+ let previous_last_output = agent
2524
+ .get("last_output_at")
2525
+ .and_then(Value::as_str)
2526
+ .map(str::to_string);
2367
2527
  let Some(agent_obj) = agent.as_object_mut() else {
2368
2528
  return previous_last_output;
2369
2529
  };