@team-agent/installer 0.4.11 → 0.5.1

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 (80) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +1 -0
  4. package/crates/team-agent/src/cli/diagnose.rs +2 -15
  5. package/crates/team-agent/src/cli/emit.rs +98 -5
  6. package/crates/team-agent/src/cli/mod.rs +2 -0
  7. package/crates/team-agent/src/cli/named_address.rs +864 -0
  8. package/crates/team-agent/src/cli/send.rs +104 -0
  9. package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -0
  10. package/crates/team-agent/src/cli/tests/mod.rs +1 -0
  11. package/crates/team-agent/src/cli/tests/named_address.rs +455 -0
  12. package/crates/team-agent/src/cli/tests/status_send.rs +1 -0
  13. package/crates/team-agent/src/cli/types.rs +4 -0
  14. package/crates/team-agent/src/compiler.rs +11 -20
  15. package/crates/team-agent/src/coordinator/orphan.rs +2 -2
  16. package/crates/team-agent/src/coordinator/runtime_detectors.rs +5 -4
  17. package/crates/team-agent/src/coordinator/steps/abnormal.rs +1776 -2
  18. package/crates/team-agent/src/coordinator/steps/mod.rs +19 -0
  19. package/crates/team-agent/src/coordinator/tests/a0_lostupdate.rs +78 -7
  20. package/crates/team-agent/src/coordinator/tests/abnormal.rs +195 -0
  21. package/crates/team-agent/src/coordinator/tick.rs +31 -932
  22. package/crates/team-agent/src/diagnose/orphans.rs +66 -8
  23. package/crates/team-agent/src/layout/worker_window_helpers.rs +5 -7
  24. package/crates/team-agent/src/leader/provider_attribution.rs +7 -5
  25. package/crates/team-agent/src/leader/rediscover.rs +1 -11
  26. package/crates/team-agent/src/lifecycle/launch/plan.rs +19 -8
  27. package/crates/team-agent/src/lifecycle/launch.rs +135 -58
  28. package/crates/team-agent/src/lifecycle/lock.rs +302 -0
  29. package/crates/team-agent/src/lifecycle/mod.rs +1 -0
  30. package/crates/team-agent/src/lifecycle/profile_smoke.rs +1 -11
  31. package/crates/team-agent/src/lifecycle/restart/agent.rs +169 -113
  32. package/crates/team-agent/src/lifecycle/restart/common.rs +108 -17
  33. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +165 -32
  34. package/crates/team-agent/src/lifecycle/restart/remove.rs +16 -2
  35. package/crates/team-agent/src/lifecycle/restart/selection.rs +2 -1
  36. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +6 -0
  37. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +3 -3
  38. package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +321 -0
  39. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +35 -5
  40. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +802 -0
  41. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +675 -0
  42. package/crates/team-agent/src/lifecycle/tests.rs +3 -0
  43. package/crates/team-agent/src/lifecycle/types.rs +8 -0
  44. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +24 -9
  45. package/crates/team-agent/src/mcp_server/tools.rs +157 -68
  46. package/crates/team-agent/src/messaging/activity.rs +43 -18
  47. package/crates/team-agent/src/messaging/delivery.rs +161 -97
  48. package/crates/team-agent/src/messaging/helpers.rs +1 -0
  49. package/crates/team-agent/src/messaging/leader_receiver.rs +22 -1
  50. package/crates/team-agent/src/messaging/results.rs +34 -9
  51. package/crates/team-agent/src/messaging/scheduler.rs +52 -9
  52. package/crates/team-agent/src/model/spec.rs +10 -6
  53. package/crates/team-agent/src/provider/adapter.rs +10 -1038
  54. package/crates/team-agent/src/provider/adapters/claude.rs +3 -8
  55. package/crates/team-agent/src/provider/adapters/copilot.rs +2 -5
  56. package/crates/team-agent/src/provider/classify.rs +23 -48
  57. package/crates/team-agent/src/provider/command.rs +16 -7
  58. package/crates/team-agent/src/provider/faults.rs +93 -53
  59. package/crates/team-agent/src/provider/session/capture.rs +4 -15
  60. package/crates/team-agent/src/provider/session_scan/claude.rs +309 -0
  61. package/crates/team-agent/src/provider/session_scan/codex.rs +40 -0
  62. package/crates/team-agent/src/provider/session_scan/common.rs +350 -0
  63. package/crates/team-agent/src/provider/session_scan/copilot.rs +202 -0
  64. package/crates/team-agent/src/provider/session_scan.rs +65 -27
  65. package/crates/team-agent/src/provider/tests/faults.rs +80 -0
  66. package/crates/team-agent/src/provider/types.rs +33 -1
  67. package/crates/team-agent/src/provider/wire.rs +171 -1
  68. package/crates/team-agent/src/state/identity.rs +242 -57
  69. package/crates/team-agent/src/state/identity_keys.rs +9 -12
  70. package/crates/team-agent/src/state/mod.rs +5 -1
  71. package/crates/team-agent/src/state/owner_gate.rs +59 -15
  72. package/crates/team-agent/src/state/ownership.rs +12 -11
  73. package/crates/team-agent/src/state/paths.rs +13 -6
  74. package/crates/team-agent/src/state/persist.rs +671 -128
  75. package/crates/team-agent/src/state/projection.rs +240 -49
  76. package/crates/team-agent/src/state/selector.rs +11 -5
  77. package/crates/team-agent/src/tmux_backend/tests.rs +51 -2
  78. package/crates/team-agent/src/tmux_backend.rs +42 -5
  79. package/crates/team-agent/src/transport/test_support.rs +55 -6
  80. package/package.json +4 -4
@@ -3,14 +3,9 @@
3
3
  //! Extracted from `provider/adapter.rs` (0.4.x decoupling step 2). Pure
4
4
  //! extraction — byte-identical to the original inline forms. Scope kept
5
5
  //! small on purpose: base command + permission/disallowed-tool mapping +
6
- //! launch wrapper. Auth hints (`claude_auth_hint`), capture-related
7
- //! helpers (`claude_projects_dir_for_cwd`, `encode_claude_projects_dir`,
8
- //! `rollout_path_has_claude_leader_marker`,
9
- //! `claude_records_have_leader_marker`), and the context-aware model
10
- //! resolver (`claude_context_model`) stay in `adapter.rs` because they
11
- //! depend on adapter-private utilities (capture scanning, `command_on_path`,
12
- //! `ProfileLaunchContext`). A later step can move those into a session
13
- //! store / auth descriptor hook.
6
+ //! launch wrapper. Auth hints (`claude_auth_hint`) and the context-aware model
7
+ //! resolver (`claude_context_model`) stay in `adapter.rs`; capture scanning
8
+ //! helpers now live under `provider/session_scan/claude.rs`.
14
9
 
15
10
  use crate::model::enums::AuthMode;
16
11
  use crate::provider::adapter::{
@@ -3,11 +3,8 @@
3
3
  //! Extracted from `provider/adapter.rs` (0.4.x decoupling step 2). Pure
4
4
  //! extraction — byte-identical to the original inline forms. Scope kept
5
5
  //! small: base command + resume + permission flags + MCP type→transport
6
- //! translation. Auth hint (`copilot_auth_hint`) and session store scan
7
- //! (`scan_copilot_session_store`, `copilot_candidate`) stay in
8
- //! `adapter.rs` because they depend on `command_on_path` /
9
- //! `CaptureSessionContext` / sqlite session-store helpers. Step 3 of the
10
- //! decoupling plan can move those into provider session store hooks.
6
+ //! translation. Auth hint (`copilot_auth_hint`) stays in `adapter.rs`;
7
+ //! session-store scanning lives under `provider/session_scan/copilot.rs`.
11
8
 
12
9
  use crate::model::enums::AuthMode;
13
10
  use crate::provider::McpConfig;
@@ -184,10 +184,22 @@ fn extract_xml_tag(text: &str, tag: &str) -> Option<String> {
184
184
  }
185
185
 
186
186
  pub fn latest_explicit_error_fact(provider: Provider, session_log_text: &str) -> Option<FaultFact> {
187
- let record = latest_jsonl_record(session_log_text)?;
187
+ session_log_text
188
+ .lines()
189
+ .rev()
190
+ .map(str::trim)
191
+ .filter(|line| !line.is_empty())
192
+ .filter_map(|line| serde_json::from_str::<serde_json::Value>(line).ok())
193
+ .find_map(|record| latest_explicit_error_fact_for_record(provider, &record))
194
+ }
195
+
196
+ fn latest_explicit_error_fact_for_record(
197
+ provider: Provider,
198
+ record: &serde_json::Value,
199
+ ) -> Option<FaultFact> {
188
200
  match provider {
189
- Provider::Codex => codex_latest_explicit_error_fact(&record),
190
- Provider::Claude | Provider::ClaudeCode => claude_latest_explicit_error_fact(&record),
201
+ Provider::Codex => codex_latest_explicit_error_fact(record),
202
+ Provider::Claude | Provider::ClaudeCode => claude_latest_explicit_error_fact(record),
191
203
  // C-3-5 cr verdict: copilot N35 通知不依赖 turn-state 分类;一期 Unknown,
192
204
  // 与 GeminiCli/Fake 同精神。二期接 sqlite turns 表后再回填。
193
205
  Provider::Copilot | Provider::GeminiCli | Provider::Fake => None,
@@ -302,15 +314,6 @@ fn extract_lifecycle_facts(provider: Provider, records: &[serde_json::Value]) ->
302
314
  .collect()
303
315
  }
304
316
 
305
- fn latest_jsonl_record(text: &str) -> Option<serde_json::Value> {
306
- let last = text
307
- .lines()
308
- .rev()
309
- .map(str::trim)
310
- .find(|line| !line.is_empty())?;
311
- serde_json::from_str::<serde_json::Value>(last).ok()
312
- }
313
-
314
317
  fn codex_latest_explicit_error_fact(record: &serde_json::Value) -> Option<FaultFact> {
315
318
  let method = record.get("method").and_then(serde_json::Value::as_str);
316
319
  if method.is_some_and(|method| method.ends_with("requestApproval")) {
@@ -323,46 +326,18 @@ fn codex_latest_explicit_error_fact(record: &serde_json::Value) -> Option<FaultF
323
326
  if turn.get("status").and_then(serde_json::Value::as_str) != Some("failed") {
324
327
  return None;
325
328
  }
326
- Some(FaultFact {
327
- signature: Signature::new("turn_failed"),
328
- turn_id: turn.get("id").and_then(serde_json::Value::as_str).map(TurnId::new),
329
- kind: FactKind::Failed,
330
- })
329
+ Some(FaultFact::new(
330
+ Signature::new("turn_failed"),
331
+ turn.get("id").and_then(serde_json::Value::as_str).map(TurnId::new),
332
+ FactKind::Failed,
333
+ ))
331
334
  }
332
335
 
333
336
  fn claude_latest_explicit_error_fact(record: &serde_json::Value) -> Option<FaultFact> {
334
- if claude_record_has_error_tool_result(record) {
335
- return None;
336
- }
337
- if record.get("type").and_then(serde_json::Value::as_str) != Some("system")
338
- || record.get("subtype").and_then(serde_json::Value::as_str) != Some("api_error")
339
- || record.get("level").and_then(serde_json::Value::as_str) != Some("error")
340
- {
337
+ if super::faults::claude_record_has_error_tool_result(record) {
341
338
  return None;
342
339
  }
343
- Some(FaultFact {
344
- signature: Signature::new("api_error"),
345
- turn_id: record
346
- .get("sessionId")
347
- .or_else(|| record.get("parentUuid"))
348
- .or_else(|| record.get("uuid"))
349
- .and_then(serde_json::Value::as_str)
350
- .map(TurnId::new),
351
- kind: FactKind::Error,
352
- })
353
- }
354
-
355
- fn claude_record_has_error_tool_result(record: &serde_json::Value) -> bool {
356
- record
357
- .get("message")
358
- .and_then(|m| m.get("content"))
359
- .and_then(serde_json::Value::as_array)
360
- .is_some_and(|items| {
361
- items.iter().any(|item| {
362
- item.get("type").and_then(serde_json::Value::as_str) == Some("tool_result")
363
- && item.get("is_error").and_then(serde_json::Value::as_bool) == Some(true)
364
- })
365
- })
340
+ super::faults::claude_explicit_error_fact(record)
366
341
  }
367
342
 
368
343
  fn decide_state(fact: &LifecycleFact, process: ProcessLiveness) -> ClassifyResult {
@@ -443,7 +418,7 @@ fn claude_lifecycle_fact(record: &serde_json::Value) -> Option<LifecycleFact> {
443
418
  vec!["api_error".to_string()],
444
419
  ));
445
420
  }
446
- if record_type == Some("user") && claude_record_has_error_tool_result(record) {
421
+ if record_type == Some("user") && super::faults::claude_record_has_error_tool_result(record) {
447
422
  return Some(lifecycle(
448
423
  FactKind::Error,
449
424
  record
@@ -39,10 +39,7 @@ pub enum CommandPlanKind {
39
39
  pub fn honors_session_id_for_base_spawn(provider: Provider) -> bool {
40
40
  matches!(
41
41
  provider,
42
- Provider::Codex
43
- | Provider::Claude
44
- | Provider::ClaudeCode
45
- | Provider::Copilot
42
+ Provider::Claude | Provider::ClaudeCode | Provider::Copilot
46
43
  )
47
44
  }
48
45
 
@@ -56,9 +53,21 @@ mod tests {
56
53
  }
57
54
 
58
55
  #[test]
59
- fn real_providers_honor_session_id() {
60
- for p in [Provider::Codex, Provider::Claude, Provider::Copilot] {
61
- assert!(honors_session_id_for_base_spawn(p));
56
+ fn provider_matrix_for_base_spawn_session_id_support() {
57
+ let cases = [
58
+ (Provider::Claude, true),
59
+ (Provider::ClaudeCode, true),
60
+ (Provider::Copilot, true),
61
+ (Provider::Codex, false),
62
+ (Provider::GeminiCli, false),
63
+ (Provider::Fake, false),
64
+ ];
65
+ for (provider, expected) in cases {
66
+ assert_eq!(
67
+ honors_session_id_for_base_spawn(provider),
68
+ expected,
69
+ "{provider:?}"
70
+ );
62
71
  }
63
72
  }
64
73
 
@@ -5,7 +5,7 @@ use super::Provider;
5
5
 
6
6
  /// `provider_state.read_fault_facts(provider, records)`(`__init__.py:46`)。
7
7
  /// 从已解析 records 抽取 fault/approval facts(kind ∈ {error, failed, approval}),
8
- /// 携 `(signature, turn_id)` C8 dedup key。`api_error` 无 ids 时 `turn_id == None`。
8
+ /// 携 `(signature, turn_id)` C8 dedup key。旧 `api_error` 无 ids 时 `turn_id == None`。
9
9
  pub fn read_fault_facts(records: &[serde_json::Value], provider: Provider) -> Vec<FaultFact> {
10
10
  records
11
11
  .iter()
@@ -22,23 +22,30 @@ pub fn explicit_error_fact(record: &serde_json::Value, provider: Provider) -> Op
22
22
  }
23
23
  }
24
24
 
25
- fn claude_explicit_error_fact(record: &serde_json::Value) -> Option<FaultFact> {
26
- if record.get("type").and_then(serde_json::Value::as_str) != Some("system")
27
- || record.get("subtype").and_then(serde_json::Value::as_str) != Some("api_error")
28
- || record.get("level").and_then(serde_json::Value::as_str) != Some("error")
29
- {
30
- return None;
25
+ pub(crate) fn claude_explicit_error_fact(record: &serde_json::Value) -> Option<FaultFact> {
26
+ if claude_record_is_system_api_error(record) {
27
+ return Some(FaultFact::new(
28
+ Signature::new("api_error"),
29
+ claude_system_api_error_turn_id(record),
30
+ FactKind::Error,
31
+ ));
31
32
  }
32
- Some(FaultFact {
33
- signature: Signature::new("api_error"),
34
- turn_id: record
35
- .get("sessionId")
36
- .or_else(|| record.get("parentUuid"))
37
- .or_else(|| record.get("uuid"))
38
- .and_then(serde_json::Value::as_str)
39
- .map(TurnId::new),
40
- kind: FactKind::Error,
41
- })
33
+ if claude_record_is_assistant_api_error(record) {
34
+ return Some(
35
+ FaultFact::new(
36
+ Signature::new("api_error"),
37
+ claude_assistant_api_error_turn_id(record),
38
+ FactKind::Error,
39
+ )
40
+ .with_api_error_details(
41
+ record.get("apiErrorStatus").and_then(serde_json::Value::as_i64),
42
+ non_empty_string_field(record, "error"),
43
+ non_empty_string_field(record, "requestId"),
44
+ non_empty_string_field(record, "uuid"),
45
+ ),
46
+ );
47
+ }
48
+ None
42
49
  }
43
50
 
44
51
  fn codex_explicit_error_fact(record: &serde_json::Value) -> Option<FaultFact> {
@@ -49,11 +56,11 @@ fn codex_explicit_error_fact(record: &serde_json::Value) -> Option<FaultFact> {
49
56
  if turn.get("status").and_then(serde_json::Value::as_str) != Some("failed") {
50
57
  return None;
51
58
  }
52
- Some(FaultFact {
53
- signature: Signature::new("turn_failed"),
54
- turn_id: turn.get("id").and_then(serde_json::Value::as_str).map(TurnId::new),
55
- kind: FactKind::Failed,
56
- })
59
+ Some(FaultFact::new(
60
+ Signature::new("turn_failed"),
61
+ turn.get("id").and_then(serde_json::Value::as_str).map(TurnId::new),
62
+ FactKind::Failed,
63
+ ))
57
64
  }
58
65
 
59
66
  fn fault_fact(provider: Provider, record: &serde_json::Value) -> Option<FaultFact> {
@@ -65,38 +72,26 @@ fn fault_fact(provider: Provider, record: &serde_json::Value) -> Option<FaultFac
65
72
  }
66
73
 
67
74
  fn claude_fault_fact(record: &serde_json::Value) -> Option<FaultFact> {
68
- if record.get("type").and_then(serde_json::Value::as_str) == Some("system")
69
- && record.get("subtype").and_then(serde_json::Value::as_str) == Some("api_error")
70
- && record.get("level").and_then(serde_json::Value::as_str) == Some("error")
71
- {
72
- return Some(FaultFact {
73
- signature: Signature::new("api_error"),
74
- turn_id: record
75
- .get("sessionId")
76
- .or_else(|| record.get("parentUuid"))
77
- .or_else(|| record.get("uuid"))
78
- .and_then(serde_json::Value::as_str)
79
- .map(TurnId::new),
80
- kind: FactKind::Error,
81
- });
75
+ if let Some(fact) = claude_explicit_error_fact(record) {
76
+ return Some(fact);
82
77
  }
83
78
  if record.get("type").and_then(serde_json::Value::as_str) == Some("user")
84
- && claude_has_error_tool_result(record)
79
+ && claude_record_has_error_tool_result(record)
85
80
  {
86
- return Some(FaultFact {
87
- signature: Signature::new("tool_result_is_error"),
88
- turn_id: record
81
+ return Some(FaultFact::new(
82
+ Signature::new("tool_result_is_error"),
83
+ record
89
84
  .get("parentUuid")
90
85
  .or_else(|| record.get("uuid"))
91
86
  .and_then(serde_json::Value::as_str)
92
87
  .map(TurnId::new),
93
- kind: FactKind::Error,
94
- });
88
+ FactKind::Error,
89
+ ));
95
90
  }
96
91
  None
97
92
  }
98
93
 
99
- fn claude_has_error_tool_result(record: &serde_json::Value) -> bool {
94
+ pub(crate) fn claude_record_has_error_tool_result(record: &serde_json::Value) -> bool {
100
95
  record
101
96
  .get("message")
102
97
  .and_then(|m| m.get("content"))
@@ -109,29 +104,74 @@ fn claude_has_error_tool_result(record: &serde_json::Value) -> bool {
109
104
  })
110
105
  }
111
106
 
107
+ fn claude_record_is_system_api_error(record: &serde_json::Value) -> bool {
108
+ record.get("type").and_then(serde_json::Value::as_str) == Some("system")
109
+ && record.get("subtype").and_then(serde_json::Value::as_str) == Some("api_error")
110
+ && record.get("level").and_then(serde_json::Value::as_str) == Some("error")
111
+ }
112
+
113
+ fn claude_system_api_error_turn_id(record: &serde_json::Value) -> Option<TurnId> {
114
+ record
115
+ .get("sessionId")
116
+ .or_else(|| record.get("parentUuid"))
117
+ .or_else(|| record.get("uuid"))
118
+ .and_then(serde_json::Value::as_str)
119
+ .map(TurnId::new)
120
+ }
121
+
122
+ fn claude_record_is_assistant_api_error(record: &serde_json::Value) -> bool {
123
+ record.get("type").and_then(serde_json::Value::as_str) == Some("assistant")
124
+ && record
125
+ .get("message")
126
+ .and_then(|message| message.get("role"))
127
+ .and_then(serde_json::Value::as_str)
128
+ == 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()
131
+ || non_empty_string_field(record, "error").is_some()
132
+ || non_empty_string_field(record, "requestId").is_some())
133
+ }
134
+
135
+ fn claude_assistant_api_error_turn_id(record: &serde_json::Value) -> Option<TurnId> {
136
+ record
137
+ .get("uuid")
138
+ .or_else(|| record.get("parentUuid"))
139
+ .or_else(|| record.get("sessionId"))
140
+ .and_then(serde_json::Value::as_str)
141
+ .map(TurnId::new)
142
+ }
143
+
144
+ fn non_empty_string_field(record: &serde_json::Value, key: &str) -> Option<String> {
145
+ record
146
+ .get(key)
147
+ .and_then(serde_json::Value::as_str)
148
+ .filter(|value| !value.is_empty())
149
+ .map(str::to_string)
150
+ }
151
+
112
152
  fn codex_fault_fact(record: &serde_json::Value) -> Option<FaultFact> {
113
153
  let method = record.get("method").and_then(serde_json::Value::as_str);
114
154
  if method == Some("turn/completed") {
115
155
  let turn = record.get("params").and_then(|p| p.get("turn"))?;
116
156
  if turn.get("status").and_then(serde_json::Value::as_str) == Some("failed") {
117
- return Some(FaultFact {
118
- signature: Signature::new("turn_failed"),
119
- turn_id: turn.get("id").and_then(serde_json::Value::as_str).map(TurnId::new),
120
- kind: FactKind::Failed,
121
- });
157
+ return Some(FaultFact::new(
158
+ Signature::new("turn_failed"),
159
+ turn.get("id").and_then(serde_json::Value::as_str).map(TurnId::new),
160
+ FactKind::Failed,
161
+ ));
122
162
  }
123
163
  }
124
164
  if method.is_some_and(|m| m.ends_with("requestApproval")) {
125
- return Some(FaultFact {
126
- signature: Signature::new("approval_required"),
127
- turn_id: record
165
+ return Some(FaultFact::new(
166
+ Signature::new("approval_required"),
167
+ record
128
168
  .get("params")
129
169
  .and_then(|p| p.get("turnId"))
130
170
  .or_else(|| record.get("params").and_then(|p| p.get("turn_id")))
131
171
  .and_then(serde_json::Value::as_str)
132
172
  .map(TurnId::new),
133
- kind: FactKind::Approval,
134
- });
173
+ FactKind::Approval,
174
+ ));
135
175
  }
136
176
  None
137
177
  }
@@ -7,6 +7,7 @@ use crate::provider::{
7
7
  CapturedSession, CapturedSessionCandidate, CaptureSessionContext, Provider, ProviderAdapter,
8
8
  ProviderError, SessionId,
9
9
  };
10
+ use crate::provider::wire::{is_claude_family, parse_provider};
10
11
 
11
12
  pub const SESSION_CAPTURE_CONVERGENCE_DEADLINE_MS: u64 = 12_000;
12
13
  pub const SESSION_CAPTURE_CONVERGENCE_POLL_MS: u64 = 250;
@@ -499,7 +500,7 @@ pub fn recover_resume_session_from_events(
499
500
  };
500
501
  // E57 postflight (lane-046-capture-gap): the capture allocator already
501
502
  // refuses Claude leader transcripts (P0 d39b5104,
502
- // claude_records_have_leader_marker in provider/adapter.rs). Event-log
503
+ // provider/session_scan/claude.rs leader marker). Event-log
503
504
  // repair must apply the SAME filter — otherwise a stale `session.captured`
504
505
  // event from a pre-fix run (or from a window where the allocator was
505
506
  // bypassed) still pulls the 590MB leader transcript onto a worker on the
@@ -507,7 +508,7 @@ pub fn recover_resume_session_from_events(
507
508
  // release-engineer via captured_via=event_log_repair). The check is
508
509
  // a no-op for non-Claude providers.
509
510
  if let Some(p) = provider {
510
- if crate::provider::adapter::rollout_path_has_claude_leader_marker(p, &rollout_path) {
511
+ if crate::provider::session_scan::rollout_path_has_claude_leader_marker(p, &rollout_path) {
511
512
  continue;
512
513
  }
513
514
  }
@@ -752,7 +753,7 @@ fn agent_session_complete(agent: &Value) -> bool {
752
753
  return false;
753
754
  }
754
755
  let provider_wire = agent.get("provider").and_then(Value::as_str).unwrap_or("");
755
- if matches!(provider_wire, "claude" | "claude-code" | "claude_code") {
756
+ if parse_provider(provider_wire).is_some_and(is_claude_family) {
756
757
  return claude_rollout_has_lifecycle_records(path);
757
758
  }
758
759
  true
@@ -1126,18 +1127,6 @@ fn captured_provider_session_keys(captured: &CapturedSession) -> BTreeSet<String
1126
1127
  keys
1127
1128
  }
1128
1129
 
1129
- fn parse_provider(raw: &str) -> Option<Provider> {
1130
- match raw {
1131
- "claude" => Some(Provider::Claude),
1132
- "claude_code" => Some(Provider::ClaudeCode),
1133
- "codex" => Some(Provider::Codex),
1134
- "copilot" => Some(Provider::Copilot),
1135
- "gemini_cli" => Some(Provider::GeminiCli),
1136
- "fake" => Some(Provider::Fake),
1137
- _ => None,
1138
- }
1139
- }
1140
-
1141
1130
  #[cfg(test)]
1142
1131
  pub(crate) mod test_support {
1143
1132
  use super::*;