@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +1 -0
- package/crates/team-agent/src/cli/diagnose.rs +2 -15
- package/crates/team-agent/src/cli/emit.rs +98 -5
- package/crates/team-agent/src/cli/mod.rs +2 -0
- package/crates/team-agent/src/cli/named_address.rs +864 -0
- package/crates/team-agent/src/cli/send.rs +104 -0
- package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -0
- package/crates/team-agent/src/cli/tests/mod.rs +1 -0
- package/crates/team-agent/src/cli/tests/named_address.rs +455 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +1 -0
- package/crates/team-agent/src/cli/types.rs +4 -0
- package/crates/team-agent/src/compiler.rs +11 -20
- package/crates/team-agent/src/coordinator/orphan.rs +2 -2
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +5 -4
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +1776 -2
- package/crates/team-agent/src/coordinator/steps/mod.rs +19 -0
- package/crates/team-agent/src/coordinator/tests/a0_lostupdate.rs +78 -7
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +195 -0
- package/crates/team-agent/src/coordinator/tick.rs +31 -932
- package/crates/team-agent/src/diagnose/orphans.rs +66 -8
- package/crates/team-agent/src/layout/worker_window_helpers.rs +5 -7
- package/crates/team-agent/src/leader/provider_attribution.rs +7 -5
- package/crates/team-agent/src/leader/rediscover.rs +1 -11
- package/crates/team-agent/src/lifecycle/launch/plan.rs +19 -8
- package/crates/team-agent/src/lifecycle/launch.rs +135 -58
- package/crates/team-agent/src/lifecycle/lock.rs +302 -0
- package/crates/team-agent/src/lifecycle/mod.rs +1 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +1 -11
- package/crates/team-agent/src/lifecycle/restart/agent.rs +169 -113
- package/crates/team-agent/src/lifecycle/restart/common.rs +108 -17
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +165 -32
- package/crates/team-agent/src/lifecycle/restart/remove.rs +16 -2
- package/crates/team-agent/src/lifecycle/restart/selection.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +6 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +3 -3
- package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +321 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +35 -5
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +802 -0
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +675 -0
- package/crates/team-agent/src/lifecycle/tests.rs +3 -0
- package/crates/team-agent/src/lifecycle/types.rs +8 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +24 -9
- package/crates/team-agent/src/mcp_server/tools.rs +157 -68
- package/crates/team-agent/src/messaging/activity.rs +43 -18
- package/crates/team-agent/src/messaging/delivery.rs +161 -97
- package/crates/team-agent/src/messaging/helpers.rs +1 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +22 -1
- package/crates/team-agent/src/messaging/results.rs +34 -9
- package/crates/team-agent/src/messaging/scheduler.rs +52 -9
- package/crates/team-agent/src/model/spec.rs +10 -6
- package/crates/team-agent/src/provider/adapter.rs +10 -1038
- package/crates/team-agent/src/provider/adapters/claude.rs +3 -8
- package/crates/team-agent/src/provider/adapters/copilot.rs +2 -5
- package/crates/team-agent/src/provider/classify.rs +23 -48
- package/crates/team-agent/src/provider/command.rs +16 -7
- package/crates/team-agent/src/provider/faults.rs +93 -53
- package/crates/team-agent/src/provider/session/capture.rs +4 -15
- package/crates/team-agent/src/provider/session_scan/claude.rs +309 -0
- package/crates/team-agent/src/provider/session_scan/codex.rs +40 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +350 -0
- package/crates/team-agent/src/provider/session_scan/copilot.rs +202 -0
- package/crates/team-agent/src/provider/session_scan.rs +65 -27
- package/crates/team-agent/src/provider/tests/faults.rs +80 -0
- package/crates/team-agent/src/provider/types.rs +33 -1
- package/crates/team-agent/src/provider/wire.rs +171 -1
- package/crates/team-agent/src/state/identity.rs +242 -57
- package/crates/team-agent/src/state/identity_keys.rs +9 -12
- package/crates/team-agent/src/state/mod.rs +5 -1
- package/crates/team-agent/src/state/owner_gate.rs +59 -15
- package/crates/team-agent/src/state/ownership.rs +12 -11
- package/crates/team-agent/src/state/paths.rs +13 -6
- package/crates/team-agent/src/state/persist.rs +671 -128
- package/crates/team-agent/src/state/projection.rs +240 -49
- package/crates/team-agent/src/state/selector.rs +11 -5
- package/crates/team-agent/src/tmux_backend/tests.rs +51 -2
- package/crates/team-agent/src/tmux_backend.rs +42 -5
- package/crates/team-agent/src/transport/test_support.rs +55 -6
- 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`)
|
|
7
|
-
//!
|
|
8
|
-
//! `
|
|
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`)
|
|
7
|
-
//!
|
|
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
|
-
|
|
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(
|
|
190
|
-
Provider::Claude | Provider::ClaudeCode => claude_latest_explicit_error_fact(
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
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::
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.
|
|
40
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
69
|
-
|
|
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
|
-
&&
|
|
79
|
+
&& claude_record_has_error_tool_result(record)
|
|
85
80
|
{
|
|
86
|
-
return Some(FaultFact
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
94
|
-
|
|
88
|
+
FactKind::Error,
|
|
89
|
+
));
|
|
95
90
|
}
|
|
96
91
|
None
|
|
97
92
|
}
|
|
98
93
|
|
|
99
|
-
fn
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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::
|
|
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
|
|
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::*;
|