@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +129 -54
- package/crates/team-agent/src/cli/diagnose.rs +1 -2
- package/crates/team-agent/src/cli/emit.rs +1 -7
- package/crates/team-agent/src/cli/helpers.rs +3 -1
- package/crates/team-agent/src/cli/leader.rs +2 -1
- package/crates/team-agent/src/cli/mod.rs +126 -16
- package/crates/team-agent/src/cli/named_address.rs +14 -5
- package/crates/team-agent/src/cli/profile.rs +19 -7
- package/crates/team-agent/src/cli/send.rs +9 -3
- package/crates/team-agent/src/cli/status.rs +8 -30
- package/crates/team-agent/src/cli/status_port.rs +1341 -1272
- package/crates/team-agent/src/cli/tests/base.rs +738 -660
- package/crates/team-agent/src/cli/tests/compile.rs +45 -18
- package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
- package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
- package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
- package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
- package/crates/team-agent/src/cli/tests/mod.rs +17 -7
- package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
- package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
- package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
- package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
- package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
- package/crates/team-agent/src/codex_app_server.rs +2 -5
- package/crates/team-agent/src/compiler/tests.rs +139 -33
- package/crates/team-agent/src/compiler.rs +55 -22
- package/crates/team-agent/src/conpty/backend.rs +23 -33
- package/crates/team-agent/src/coordinator/backoff.rs +2 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
- package/crates/team-agent/src/coordinator/health.rs +46 -31
- package/crates/team-agent/src/coordinator/mod.rs +3 -3
- package/crates/team-agent/src/coordinator/orphan.rs +22 -10
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +51 -56
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
- package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
- package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
- package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
- package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
- package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
- package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
- package/crates/team-agent/src/db/message_store.rs +138 -30
- package/crates/team-agent/src/db/migration.rs +249 -61
- package/crates/team-agent/src/db/schema.rs +303 -82
- package/crates/team-agent/src/diagnose/comms.rs +9 -2
- package/crates/team-agent/src/diagnose/mod.rs +1 -3
- package/crates/team-agent/src/diagnose/orphans.rs +79 -61
- package/crates/team-agent/src/event_log.rs +70 -16
- package/crates/team-agent/src/layout/manager.rs +15 -4
- package/crates/team-agent/src/layout/mod.rs +4 -4
- package/crates/team-agent/src/layout/overlay.rs +10 -3
- package/crates/team-agent/src/layout/placement.rs +5 -1
- package/crates/team-agent/src/layout/recovery.rs +4 -2
- package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
- package/crates/team-agent/src/layout/sessions.rs +17 -9
- package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
- package/crates/team-agent/src/layout/worker_env.rs +87 -19
- package/crates/team-agent/src/leader/helpers.rs +7 -1
- package/crates/team-agent/src/leader/lease.rs +199 -89
- package/crates/team-agent/src/leader/owner_bind.rs +55 -22
- package/crates/team-agent/src/leader/provider_attribution.rs +25 -6
- package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
- package/crates/team-agent/src/leader/rediscover.rs +74 -25
- package/crates/team-agent/src/leader/registry.rs +1 -1
- package/crates/team-agent/src/leader/start.rs +75 -54
- package/crates/team-agent/src/leader/takeover.rs +46 -11
- package/crates/team-agent/src/leader/tests/basics.rs +320 -167
- package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
- package/crates/team-agent/src/leader/tests/identity.rs +428 -356
- package/crates/team-agent/src/leader/tests/idle.rs +285 -254
- package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
- package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
- package/crates/team-agent/src/leader/tests/mod.rs +115 -99
- package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
- package/crates/team-agent/src/lib.rs +4 -4
- package/crates/team-agent/src/lifecycle/display.rs +7 -3
- package/crates/team-agent/src/lifecycle/launch.rs +55 -15
- package/crates/team-agent/src/lifecycle/mod.rs +9 -1
- package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
- package/crates/team-agent/src/lifecycle/restart/common.rs +6 -2
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests.rs +2 -2
- package/crates/team-agent/src/main.rs +4 -4
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
- package/crates/team-agent/src/mcp_server/mod.rs +11 -2
- package/crates/team-agent/src/mcp_server/types.rs +14 -3
- package/crates/team-agent/src/mcp_server/wire.rs +230 -68
- package/crates/team-agent/src/messaging/delivery.rs +20 -18
- package/crates/team-agent/src/messaging/helpers.rs +25 -4
- package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
- package/crates/team-agent/src/messaging/mod.rs +2 -3
- package/crates/team-agent/src/messaging/selftest.rs +46 -26
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
- package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
- package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
- package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
- package/crates/team-agent/src/messaging/trust.rs +25 -2
- package/crates/team-agent/src/messaging/watchers.rs +37 -9
- package/crates/team-agent/src/model/enums.rs +65 -16
- package/crates/team-agent/src/model/ids.rs +12 -3
- package/crates/team-agent/src/model/paths.rs +28 -7
- package/crates/team-agent/src/model/permissions.rs +176 -33
- package/crates/team-agent/src/model/routing.rs +66 -20
- package/crates/team-agent/src/model/spec.rs +365 -69
- package/crates/team-agent/src/model/task_graph.rs +36 -9
- package/crates/team-agent/src/model/yaml/tests.rs +24 -6
- package/crates/team-agent/src/model/yaml.rs +7 -6
- package/crates/team-agent/src/packaging/install.rs +23 -9
- package/crates/team-agent/src/packaging/migrate.rs +5 -7
- package/crates/team-agent/src/packaging/mod.rs +9 -1
- package/crates/team-agent/src/packaging/repair.rs +13 -6
- package/crates/team-agent/src/packaging/tests.rs +63 -16
- package/crates/team-agent/src/packaging/types.rs +22 -7
- package/crates/team-agent/src/platform/argv.rs +4 -1
- package/crates/team-agent/src/platform/file_lock.rs +22 -8
- package/crates/team-agent/src/platform/process.rs +54 -24
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
- package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
- package/crates/team-agent/src/provider/classify.rs +127 -42
- package/crates/team-agent/src/provider/faults.rs +17 -5
- package/crates/team-agent/src/provider/helpers.rs +6 -5
- package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
- package/crates/team-agent/src/state/persist.rs +28 -0
- package/crates/team-agent/src/state/repository.rs +8 -3
- package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
- package/crates/team-agent/src/tmux_backend.rs +80 -44
- package/crates/team-agent/src/topology.rs +40 -20
- package/crates/team-agent/src/transport/test_support.rs +20 -23
- package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
- package/crates/team-agent/src/transport/tests/mod.rs +178 -187
- package/crates/team-agent/src/transport/tests/wire.rs +561 -525
- package/crates/team-agent/src/transport.rs +12 -17
- package/crates/team-agent/src/transport_factory.rs +29 -14
- 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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
286
|
+
return Ok(remind(
|
|
287
|
+
false,
|
|
288
|
+
NoPingReason::NotArmedNoWorkerTurn,
|
|
289
|
+
Vec::new(),
|
|
290
|
+
None,
|
|
291
|
+
));
|
|
255
292
|
};
|
|
256
|
-
if ms
|
|
257
|
-
|
|
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(
|
|
309
|
+
return Ok(remind(
|
|
310
|
+
false,
|
|
311
|
+
NoPingReason::NotArmedNoWorkerTurn,
|
|
312
|
+
Vec::new(),
|
|
313
|
+
None,
|
|
314
|
+
));
|
|
264
315
|
};
|
|
265
|
-
if ms
|
|
266
|
-
|
|
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(
|
|
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(
|
|
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")
|
|
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
|
|
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)
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
)
|
|
451
|
-
|
|
452
|
-
|
|
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(
|
|
456
|
-
|
|
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
|
|
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
|
|
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")
|
|
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
|
|
130
|
-
|
|
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")
|
|
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(
|
|
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
|
-
|
|
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
|
-
.
|
|
178
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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,
|
|
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 =
|
|
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!(
|
|
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!(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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() {
|
|
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(
|
|
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()
|
|
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),
|