@team-agent/installer 0.5.100 → 0.5.102

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 (63) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/diagnose.rs +29 -7
  4. package/crates/team-agent/src/cli/mod.rs +24 -8
  5. package/crates/team-agent/src/cli/tests/main_preserved.rs +0 -74
  6. package/crates/team-agent/src/compiler/tests.rs +20 -23
  7. package/crates/team-agent/src/compiler.rs +52 -65
  8. package/crates/team-agent/src/coordinator/tests/s3_bounded.rs +16 -48
  9. package/crates/team-agent/src/leader/start.rs +1 -14
  10. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +211 -16
  11. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +1 -0
  12. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +15 -5
  13. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +89 -6
  14. package/crates/team-agent/src/lifecycle/launch/role_source.rs +168 -32
  15. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -1
  16. package/crates/team-agent/src/lifecycle/launch.rs +11 -3
  17. package/crates/team-agent/src/lifecycle/profile_launch.rs +23 -8
  18. package/crates/team-agent/src/lifecycle/restart/agent.rs +2 -2
  19. package/crates/team-agent/src/lifecycle/restart/avatar.rs +1806 -0
  20. package/crates/team-agent/src/lifecycle/restart/common.rs +115 -2
  21. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +8 -2
  22. package/crates/team-agent/src/lifecycle/restart/selection.rs +14 -2
  23. package/crates/team-agent/src/lifecycle/restart.rs +11 -4
  24. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +0 -28
  25. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +11 -9
  26. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +8 -401
  27. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +2 -2
  28. package/crates/team-agent/src/lifecycle/tests/pi_fork_session_inheritance_red.rs +1398 -0
  29. package/crates/team-agent/src/lifecycle/tests.rs +1 -0
  30. package/crates/team-agent/src/lifecycle/types.rs +10 -2
  31. package/crates/team-agent/src/lifecycle/worker_command_context.rs +22 -8
  32. package/crates/team-agent/src/mcp_server/helpers.rs +0 -16
  33. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +2 -7
  34. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -127
  35. package/crates/team-agent/src/mcp_server/mod.rs +7 -12
  36. package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -132
  37. package/crates/team-agent/src/mcp_server/tests/send.rs +1 -93
  38. package/crates/team-agent/src/mcp_server/tests/tools.rs +0 -61
  39. package/crates/team-agent/src/mcp_server/tests/wire.rs +6 -51
  40. package/crates/team-agent/src/mcp_server/tests.rs +4 -200
  41. package/crates/team-agent/src/mcp_server/tools.rs +5 -396
  42. package/crates/team-agent/src/mcp_server/types.rs +3 -36
  43. package/crates/team-agent/src/mcp_server/wire.rs +2 -223
  44. package/crates/team-agent/src/messaging/leader_receiver.rs +1 -1
  45. package/crates/team-agent/src/messaging/mod.rs +2 -2
  46. package/crates/team-agent/src/messaging/scheduler.rs +2 -205
  47. package/crates/team-agent/src/messaging/tests/basic.rs +0 -31
  48. package/crates/team-agent/src/messaging/tests/main_preserved.rs +0 -73
  49. package/crates/team-agent/src/messaging/tests/runtime.rs +0 -26
  50. package/crates/team-agent/src/messaging/types.rs +0 -20
  51. package/crates/team-agent/src/model/spec.rs +15 -18
  52. package/crates/team-agent/src/provider/types.rs +2 -0
  53. package/crates/team-agent/src/state/persist.rs +58 -2
  54. package/crates/team-agent/src/state/projection.rs +5 -1
  55. package/crates/team-agent/src/state/repository/tests.rs +1 -16
  56. package/crates/team-agent/src/state/repository.rs +126 -37
  57. package/crates/team-agent/src/transport/test_support.rs +23 -3
  58. package/package.json +4 -4
  59. package/skills/team-agent/SKILL.md +18 -47
  60. package/skills/team-agent/command-coverage.json +7 -0
  61. package/skills/team-agent/references/team-agent-operator.md +28 -118
  62. package/skills/team-agent/references/team-in-team.md +4 -19
  63. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +0 -484
@@ -25,19 +25,9 @@ use super::types::{
25
25
  /// schema cannot drift from the enum. Returned verbatim by `tools/list`.
26
26
  pub fn tools_contract() -> Vec<Value> {
27
27
  let tools = [
28
- McpTool::AssignTask,
29
28
  McpTool::SendMessage,
30
29
  McpTool::ReportResult,
31
- McpTool::UpdateState,
32
30
  McpTool::GetTeamStatus,
33
- McpTool::StopAgent,
34
- McpTool::ResetAgent,
35
- McpTool::AddAgent,
36
- McpTool::CloneAgent,
37
- McpTool::ForkAgent,
38
- McpTool::RequestHuman,
39
- McpTool::StuckList,
40
- McpTool::StuckCancel,
41
31
  ];
42
32
  tools.into_iter().map(tool_contract).collect()
43
33
  }
@@ -397,21 +387,11 @@ fn tool_contract(tool: McpTool) -> Value {
397
387
  "Send a message to a teammate, the leader, or '*' for all other team members. mailbox=true stores durably without live injection; the default is live delivery.",
398
388
  vec!["to", "content"],
399
389
  ),
400
- McpTool::AssignTask => ("Assign or update a task in the team graph and deliver it to its assignee.", vec!["task"]),
401
390
  McpTool::ReportResult => (
402
391
  "Report task completion with a durable result envelope. Optional presentation routing controls live leader display, not persistence.",
403
392
  Vec::new(),
404
393
  ),
405
- McpTool::UpdateState => ("Append a note to team state and rewrite team_state.md.", vec!["note"]),
406
394
  McpTool::GetTeamStatus => ("Return machine-readable team status.", Vec::new()),
407
- McpTool::StopAgent => ("Stop a running worker.", vec!["agent_id"]),
408
- McpTool::ResetAgent => ("Reset one worker to a fresh session.", vec!["agent_id", "discard_session"]),
409
- McpTool::AddAgent => ("Add a first-class worker from a role file.", vec!["new_agent_id", "role_file_path"]),
410
- McpTool::CloneAgent => ("Clone a worker role into a fresh provider session.", vec!["source_agent_id", "as_agent_id"]),
411
- McpTool::ForkAgent => ("Fork a running worker.", vec!["source_agent_id", "as_agent_id"]),
412
- McpTool::RequestHuman => ("Ask the leader or user for human input.", vec!["question"]),
413
- McpTool::StuckList => ("List manually suppressed idle alerts.", Vec::new()),
414
- McpTool::StuckCancel => ("Suppress repeated stuck or idle alerts.", vec!["agent_id"]),
415
395
  };
416
396
  serde_json::json!({
417
397
  "name": tool.wire_name(),
@@ -428,14 +408,6 @@ fn tool_contract(tool: McpTool) -> Value {
428
408
  fn tool_properties(tool: McpTool) -> serde_json::Map<String, Value> {
429
409
  let mut properties = serde_json::Map::new();
430
410
  match tool {
431
- McpTool::AssignTask => {
432
- insert_property(&mut properties, "task", task_property());
433
- insert_property(
434
- &mut properties,
435
- "message",
436
- string_property("Optional message to deliver with the task."),
437
- );
438
- }
439
411
  McpTool::SendMessage => {
440
412
  insert_property(
441
413
  &mut properties,
@@ -506,108 +478,7 @@ fn tool_properties(tool: McpTool) -> serde_json::Map<String, Value> {
506
478
  presentation_property("Optional durable presentation routing."),
507
479
  );
508
480
  }
509
- McpTool::UpdateState => {
510
- insert_property(
511
- &mut properties,
512
- "note",
513
- string_property("Note to append to team state."),
514
- );
515
- }
516
- McpTool::GetTeamStatus | McpTool::StuckList => {}
517
- McpTool::StopAgent => {
518
- insert_property(
519
- &mut properties,
520
- "agent_id",
521
- string_property("Agent id to stop."),
522
- );
523
- }
524
- McpTool::ResetAgent => {
525
- insert_property(
526
- &mut properties,
527
- "agent_id",
528
- string_property("Agent id to reset."),
529
- );
530
- insert_property(
531
- &mut properties,
532
- "discard_session",
533
- boolean_property("Whether to discard the existing provider session."),
534
- );
535
- }
536
- McpTool::AddAgent => {
537
- insert_property(
538
- &mut properties,
539
- "new_agent_id",
540
- string_property("New agent id."),
541
- );
542
- insert_property(
543
- &mut properties,
544
- "role_file_path",
545
- string_property("Workspace-relative role file path."),
546
- );
547
- }
548
- McpTool::ForkAgent => {
549
- insert_property(
550
- &mut properties,
551
- "source_agent_id",
552
- string_property("Agent id to fork from."),
553
- );
554
- insert_property(
555
- &mut properties,
556
- "as_agent_id",
557
- string_property("Agent id for the forked worker."),
558
- );
559
- insert_property(
560
- &mut properties,
561
- "label",
562
- string_property("Optional display label."),
563
- );
564
- }
565
- McpTool::CloneAgent => {
566
- insert_property(
567
- &mut properties,
568
- "source_agent_id",
569
- string_property("Agent id to clone from."),
570
- );
571
- insert_property(
572
- &mut properties,
573
- "as_agent_id",
574
- string_property("Agent id for the cloned worker."),
575
- );
576
- insert_property(
577
- &mut properties,
578
- "label",
579
- string_property("Optional display label."),
580
- );
581
- }
582
- McpTool::RequestHuman => {
583
- insert_property(
584
- &mut properties,
585
- "question",
586
- string_property("Question to ask the human."),
587
- );
588
- insert_property(
589
- &mut properties,
590
- "task_id",
591
- string_property("Optional related task id."),
592
- );
593
- insert_property(
594
- &mut properties,
595
- "agent_id",
596
- string_property("Optional requesting agent id."),
597
- );
598
- }
599
- McpTool::StuckCancel => {
600
- insert_property(
601
- &mut properties,
602
- "agent_id",
603
- string_property("Agent id whose stuck alerts should be suppressed."),
604
- );
605
- insert_property(
606
- &mut properties,
607
- "alert_type",
608
- string_property("Alert type to suppress, or all."),
609
- );
610
- }
481
+ McpTool::GetTeamStatus => {}
611
482
  }
612
483
  properties
613
484
  }
@@ -628,25 +499,6 @@ fn object_property(description: &str) -> Value {
628
499
  serde_json::json!({"type": "object", "description": description, "additionalProperties": true})
629
500
  }
630
501
 
631
- fn task_property() -> Value {
632
- serde_json::json!({
633
- "type": "object",
634
- "description": "Task object to add or update.",
635
- "properties": {
636
- "result_route": {
637
- "type": "string",
638
- "enum": crate::messaging::results::ResultRoute::ALL
639
- .iter()
640
- .map(|route| route.as_str())
641
- .collect::<Vec<_>>(),
642
- "default": crate::messaging::results::ResultRoute::default().as_str(),
643
- "description": "Result destination chosen by the task assigner."
644
- }
645
- },
646
- "additionalProperties": true
647
- })
648
- }
649
-
650
502
  fn presentation_property(description: &str) -> Value {
651
503
  serde_json::json!({
652
504
  "type": "object",
@@ -690,14 +542,8 @@ pub(crate) fn dispatch_tool(
690
542
  }
691
543
  _ => {}
692
544
  }
693
- if scope_ceiling_tool(tool) {
694
- tools.validate_rpc_scope_args(tool.wire_name(), args)?;
695
- }
545
+ tools.validate_rpc_scope_args(tool.wire_name(), args)?;
696
546
  match tool {
697
- McpTool::AssignTask => tools.assign_task(
698
- args.get("task").unwrap_or(args),
699
- args.get("message").and_then(Value::as_str),
700
- ),
701
547
  McpTool::SendMessage => {
702
548
  let target = message_target_from_value(args.get("to"));
703
549
  let content = args.get("content").and_then(Value::as_str).unwrap_or("");
@@ -740,77 +586,10 @@ pub(crate) fn dispatch_tool(
740
586
  args.get("agent_id").and_then(Value::as_str),
741
587
  args.get("presentation"),
742
588
  ),
743
- McpTool::UpdateState => {
744
- tools.update_state(args.get("note").and_then(Value::as_str).unwrap_or(""))
745
- }
746
589
  McpTool::GetTeamStatus => tools.get_team_status(),
747
- McpTool::StopAgent => {
748
- tools.stop_agent(args.get("agent_id").and_then(Value::as_str).unwrap_or(""))
749
- }
750
- McpTool::ResetAgent => tools.reset_agent(
751
- args.get("agent_id").and_then(Value::as_str).unwrap_or(""),
752
- args.get("discard_session")
753
- .and_then(Value::as_bool)
754
- .unwrap_or(false),
755
- ),
756
- McpTool::AddAgent => tools.add_agent(
757
- args.get("new_agent_id")
758
- .and_then(Value::as_str)
759
- .unwrap_or(""),
760
- args.get("role_file_path")
761
- .and_then(Value::as_str)
762
- .unwrap_or(""),
763
- ),
764
- McpTool::ForkAgent => tools.fork_agent(
765
- args.get("source_agent_id")
766
- .and_then(Value::as_str)
767
- .unwrap_or(""),
768
- args.get("as_agent_id")
769
- .and_then(Value::as_str)
770
- .unwrap_or(""),
771
- args.get("label").and_then(Value::as_str),
772
- ),
773
- McpTool::CloneAgent => tools.clone_agent(
774
- args.get("source_agent_id")
775
- .and_then(Value::as_str)
776
- .unwrap_or(""),
777
- args.get("as_agent_id")
778
- .and_then(Value::as_str)
779
- .unwrap_or(""),
780
- args.get("label").and_then(Value::as_str),
781
- ),
782
- McpTool::RequestHuman => tools.request_human(
783
- args.get("question").and_then(Value::as_str).unwrap_or(""),
784
- args.get("task_id").and_then(Value::as_str),
785
- args.get("agent_id").and_then(Value::as_str),
786
- ),
787
- McpTool::StuckList => tools.stuck_list(),
788
- McpTool::StuckCancel => tools.stuck_cancel(
789
- args.get("agent_id").and_then(Value::as_str).unwrap_or(""),
790
- // tools.py:351 — the MCP default alert_type is "stuck", not "all".
791
- args.get("alert_type")
792
- .and_then(Value::as_str)
793
- .unwrap_or("stuck"),
794
- ),
795
590
  }
796
591
  }
797
592
 
798
- fn scope_ceiling_tool(tool: McpTool) -> bool {
799
- matches!(
800
- tool,
801
- McpTool::SendMessage
802
- | McpTool::ReportResult
803
- | McpTool::RequestHuman
804
- | McpTool::AssignTask
805
- | McpTool::UpdateState
806
- | McpTool::GetTeamStatus
807
- | McpTool::StopAgent
808
- | McpTool::ResetAgent
809
- | McpTool::CloneAgent
810
- | McpTool::ForkAgent
811
- )
812
- }
813
-
814
593
  fn message_target_from_value(value: Option<&Value>) -> MessageTarget {
815
594
  match value {
816
595
  Some(Value::String(s)) if s == "*" => MessageTarget::Broadcast,
@@ -22,7 +22,7 @@ use super::{
22
22
  };
23
23
 
24
24
  /// `_send_to_leader_receiver` (`leader.py:69`) — **N31/N32 funnel primitive**:所有 leader-bound
25
- /// caller(send_message(to=leader) / report_result / request_human / idle reminder /
25
+ /// caller(send_message(to=leader) / report_result / idle reminder /
26
26
  /// broadcast-to-leader / peer-mirror / worker.abnormal_exit)统一经过这里。
27
27
  ///
28
28
  /// 职责 = create_message + leader_notification_log dedup(result_id 时)+ audit + emit
@@ -105,14 +105,14 @@ pub use results::{
105
105
  collect, collect_for_team, report_result, report_result_for_owner_team,
106
106
  report_result_for_owner_team_with_primary_error,
107
107
  };
108
- pub use scheduler::{detect_stuck_agents, fire_due_scheduled_events, stuck_cancel, stuck_list};
108
+ pub use scheduler::{detect_stuck_agents, fire_due_scheduled_events};
109
109
  pub use selftest::{run_comms_selftest, CommsSelftestDriver};
110
110
  pub use send::{
111
111
  apply_worker_sender_bypass, send_message, MessageTarget, SendOptions, TrustedSender,
112
112
  };
113
113
  pub use trust::{attempt_trust_auto_answer, TrustAnswerOutcome};
114
114
  pub use types::{
115
- ActivityStatus, AgentActivity, AlertType, CheckEvidence, CheckKind, CheckStatus,
115
+ ActivityStatus, AgentActivity, CheckEvidence, CheckKind, CheckStatus,
116
116
  ContractSuiteCheck, DeliveryOutcome, DeliveryRefusal, DeliveryStage, DeliveryStatus,
117
117
  IdleEvaluation, LeaderNotificationKey, LeaderReceiver, PaneWidthQuery,
118
118
  ProviderSdkCalls, ReceiverMode, ScheduledKind, SelftestCheck, SelftestReport, SendEventPayload,
@@ -1,5 +1,5 @@
1
1
  //!
2
- //! scheduler.py — 调度器心脏 + stuck 检测 + 告警抑制 (card §18/§68/§73)。
2
+ //! Scheduled-event dispatch and coordinator stuck detection (card §18/§68/§73).
3
3
 
4
4
  use std::path::Path;
5
5
 
@@ -11,9 +11,7 @@ use crate::transport::{PaneId, Transport};
11
11
 
12
12
  use super::delivery::{deliver_stored_message, handle_trust_retry_needed};
13
13
  use super::helpers::{parse_scheduled_kind, status_wire};
14
- use super::{
15
- AlertType, MessagingError, ScheduledKind, TrustRetryPayload, TRUST_RETRY_MAX_ATTEMPTS,
16
- };
14
+ use super::{MessagingError, ScheduledKind, TrustRetryPayload, TRUST_RETRY_MAX_ATTEMPTS};
17
15
 
18
16
  /// `_fire_due_scheduled_events` (`scheduler.py:41`):coordinator tick 的调度器心脏 —— 分派到期
19
17
  /// `send`/`health_ping`/`trust_retry` ([`ScheduledKind`] 穷尽 match),send 失败有界重试。
@@ -209,204 +207,3 @@ fn output_is_stale(last_output_at: &str, timeout_seconds: i64) -> bool {
209
207
  .num_seconds()
210
208
  >= timeout_seconds
211
209
  }
212
-
213
- /// stuck/idle 告警列表 (`scheduler.py:222`)。team-scoped 抑制查询。CLI 调。
214
- pub fn stuck_list(workspace: &Path) -> Result<serde_json::Value, MessagingError> {
215
- let state = crate::state::persist::load_runtime_state(workspace)?;
216
- let team = active_team_key(workspace, &state);
217
- let suppressed = state
218
- .get("coordinator")
219
- .and_then(|v| v.get("suppressed_idle_alerts"))
220
- .and_then(|v| v.get(&team))
221
- .cloned()
222
- .unwrap_or_else(|| serde_json::json!({}));
223
- Ok(serde_json::json!({"ok": true, "suppressed_idle_alerts": suppressed}))
224
- }
225
-
226
- /// 取消 idle 告警抑制 (`scheduler.py:262`)。`alert_type` 为 [`AlertType`] 或 `all` (展开全集);
227
- /// owner-gate 校验 (check_team_owner)。CLI 调。
228
- pub fn stuck_cancel(
229
- workspace: &Path,
230
- agent_id: &str,
231
- alert_type: Option<AlertType>,
232
- suppressed_by: &str,
233
- ) -> Result<serde_json::Value, MessagingError> {
234
- let alert_types: Vec<&str> = match alert_type {
235
- Some(AlertType::Stuck) => vec!["stuck"],
236
- Some(AlertType::IdleFallback) => vec!["idle_fallback"],
237
- Some(AlertType::CrossWorkerDeadlock) => vec!["cross_worker_deadlock"],
238
- None => vec!["cross_worker_deadlock", "idle_fallback", "stuck"],
239
- };
240
- let mut state = crate::state::persist::load_runtime_state(workspace)?;
241
- let team = active_team_key(workspace, &state);
242
- let now = chrono::Utc::now().to_rfc3339();
243
- let assigned = assigned_task_ids(&state, agent_id);
244
- let store = MessageStore::open(workspace)?;
245
- let delivered = delivered_message_ids(&store, &team, agent_id)?;
246
- for kind in &alert_types {
247
- upsert_suppression(
248
- &mut state,
249
- SuppressionRecord {
250
- team: &team,
251
- agent_id,
252
- alert_type: kind,
253
- suppressed_by,
254
- suppressed_at: &now,
255
- assigned_task_ids: assigned.clone(),
256
- delivered_message_ids: delivered.clone(),
257
- },
258
- );
259
- }
260
- crate::state::repository::StateRepository::new(workspace).save_reapplying(
261
- crate::state::repository::StateWriteIntent::SchedulerSuppression {
262
- owner_team_id: Some(&team),
263
- },
264
- &state,
265
- |latest| {
266
- for kind in &alert_types {
267
- upsert_suppression(
268
- latest,
269
- SuppressionRecord {
270
- team: &team,
271
- agent_id,
272
- alert_type: kind,
273
- suppressed_by,
274
- suppressed_at: &now,
275
- assigned_task_ids: assigned.clone(),
276
- delivered_message_ids: delivered.clone(),
277
- },
278
- );
279
- }
280
- },
281
- )?;
282
- crate::event_log::EventLog::new(workspace).write(
283
- "coordinator.idle_alert_suppressed",
284
- serde_json::json!({
285
- "agent_id": agent_id,
286
- "team": team,
287
- "alert_types": alert_types,
288
- "suppressed_by": suppressed_by,
289
- }),
290
- )?;
291
- let suppressed = state
292
- .get("coordinator")
293
- .and_then(|v| v.get("suppressed_idle_alerts"))
294
- .and_then(|v| v.get(&team))
295
- .and_then(|v| v.get(agent_id))
296
- .cloned()
297
- .unwrap_or_else(|| serde_json::json!({}));
298
- Ok(serde_json::json!({
299
- "ok": true,
300
- "agent_id": agent_id,
301
- "alert_types": alert_types,
302
- "suppressed": suppressed
303
- }))
304
- }
305
-
306
- fn active_team_key(workspace: &Path, state: &serde_json::Value) -> String {
307
- state
308
- .get("active_team_key")
309
- .and_then(serde_json::Value::as_str)
310
- .filter(|team| !team.is_empty())
311
- .map(ToString::to_string)
312
- .or_else(|| {
313
- workspace
314
- .file_name()
315
- .map(|name| name.to_string_lossy().to_string())
316
- })
317
- .unwrap_or_else(|| "current".to_string())
318
- }
319
-
320
- fn assigned_task_ids(state: &serde_json::Value, agent_id: &str) -> Vec<String> {
321
- let mut ids: Vec<String> = state
322
- .get("tasks")
323
- .and_then(serde_json::Value::as_array)
324
- .map(|tasks| {
325
- tasks
326
- .iter()
327
- .filter(|task| {
328
- task.get("assignee").and_then(serde_json::Value::as_str) == Some(agent_id)
329
- })
330
- .filter_map(|task| {
331
- task.get("id")
332
- .and_then(serde_json::Value::as_str)
333
- .map(ToString::to_string)
334
- })
335
- .collect()
336
- })
337
- .unwrap_or_default();
338
- ids.sort();
339
- ids
340
- }
341
-
342
- fn delivered_message_ids(
343
- store: &MessageStore,
344
- team: &str,
345
- agent_id: &str,
346
- ) -> Result<Vec<String>, MessagingError> {
347
- let conn = crate::db::schema::open_db(store.db_path())?;
348
- let mut stmt = conn.prepare(
349
- "select message_id from messages
350
- where (owner_team_id = ?1 or owner_team_id is null)
351
- and recipient = ?2
352
- and status in ('visible', 'submitted', 'delivered', 'acknowledged')
353
- order by message_id",
354
- )?;
355
- let rows = stmt.query_map(params![team, agent_id], |row| row.get::<_, String>(0))?;
356
- Ok(rows.collect::<Result<Vec<_>, _>>()?)
357
- }
358
-
359
- struct SuppressionRecord<'a> {
360
- team: &'a str,
361
- agent_id: &'a str,
362
- alert_type: &'a str,
363
- suppressed_by: &'a str,
364
- suppressed_at: &'a str,
365
- assigned_task_ids: Vec<String>,
366
- delivered_message_ids: Vec<String>,
367
- }
368
-
369
- fn upsert_suppression(state: &mut serde_json::Value, record: SuppressionRecord<'_>) {
370
- let Some(root) = state.as_object_mut() else {
371
- return;
372
- };
373
- let Some(coordinator) = root
374
- .entry("coordinator")
375
- .or_insert_with(|| serde_json::json!({}))
376
- .as_object_mut()
377
- else {
378
- return;
379
- };
380
- let Some(all) = coordinator
381
- .entry("suppressed_idle_alerts")
382
- .or_insert_with(|| serde_json::json!({}))
383
- .as_object_mut()
384
- else {
385
- return;
386
- };
387
- let Some(team_map) = all
388
- .entry(record.team.to_string())
389
- .or_insert_with(|| serde_json::json!({}))
390
- .as_object_mut()
391
- else {
392
- return;
393
- };
394
- let Some(agent_map) = team_map
395
- .entry(record.agent_id.to_string())
396
- .or_insert_with(|| serde_json::json!({}))
397
- .as_object_mut()
398
- else {
399
- return;
400
- };
401
- agent_map.insert(
402
- record.alert_type.to_string(),
403
- serde_json::json!({
404
- "suppressed_at": record.suppressed_at,
405
- "suppressed_by": record.suppressed_by,
406
- "snapshot": {
407
- "assigned_task_ids": record.assigned_task_ids,
408
- "delivered_message_ids": record.delivered_message_ids
409
- },
410
- }),
411
- );
412
- }
@@ -140,23 +140,6 @@ fn activity_status_serde_byte_locked() {
140
140
  );
141
141
  }
142
142
 
143
- #[test]
144
- fn alert_type_serde_byte_locked() {
145
- // scheduler.py:38 _ALERT_TYPES set members.
146
- assert_eq!(
147
- serde_json::to_string(&AlertType::Stuck).unwrap(),
148
- "\"stuck\""
149
- );
150
- assert_eq!(
151
- serde_json::to_string(&AlertType::IdleFallback).unwrap(),
152
- "\"idle_fallback\""
153
- );
154
- assert_eq!(
155
- serde_json::to_string(&AlertType::CrossWorkerDeadlock).unwrap(),
156
- "\"cross_worker_deadlock\""
157
- );
158
- }
159
-
160
143
  #[test]
161
144
  fn check_status_serde_byte_locked() {
162
145
  assert_eq!(
@@ -266,20 +249,6 @@ fn provider_sdk_calls_is_zero_only_when_all_three_zero() {
266
249
  .is_zero());
267
250
  }
268
251
 
269
- #[test]
270
- fn alert_type_all_is_sorted_full_set() {
271
- // scheduler.py:269 sorted(_ALERT_TYPES) =
272
- // ['cross_worker_deadlock','idle_fallback','stuck'].
273
- assert_eq!(
274
- AlertType::all(),
275
- [
276
- AlertType::CrossWorkerDeadlock,
277
- AlertType::IdleFallback,
278
- AlertType::Stuck
279
- ]
280
- );
281
- }
282
-
283
252
  #[test]
284
253
  fn activity_status_idle_takeover_gate_uncertain_never_idle() {
285
254
  // THE IRON LAW (bug-071/077/085): only Idle passes; Uncertain/Working/Stuck
@@ -1,78 +1,5 @@
1
1
  use super::*;
2
2
 
3
- // ── F1 [P1 byte-shape] — stuck_cancel suppression snapshot.delivered_message_ids must use golden's
4
- // _DELIVERED_MESSAGE_STATUSES = {visible, submitted, delivered, acknowledged} (scheduler.py:27,376-383),
5
- // sorted. The Rust delivered_message_ids (scheduler.rs:243) used {injected, visible, submitted,
6
- // submitted_unverified, delivered} — the WRONG set (reviewer's explicit warning: don't copy the
7
- // activity.rs deadlock query set): it EXCLUDES 'acknowledged' and INCLUDES 'injected'/'submitted_unverified'.
8
- // (The snapshot also must carry BOTH assigned_task_ids AND delivered_message_ids — that structural half
9
- // is already present; this pins the STATUS SET.)
10
- #[test]
11
- fn stuck_cancel_snapshot_delivered_message_ids_uses_golden_status_set() {
12
- let ws = tmp_ws("f1_delivered");
13
- crate::state::persist::save_runtime_state(
14
- &ws,
15
- &serde_json::json!({
16
- "active_team_key": "teamX",
17
- "agents": {"w1": {"status": "running", "provider": "codex"}}
18
- }),
19
- )
20
- .unwrap();
21
- let store = crate::message_store::MessageStore::open(&ws).unwrap();
22
- // golden-delivered (must be IN delivered_message_ids): acknowledged + visible.
23
- let m_ack = store
24
- .create_message(None, "leader", "w1", "ack-me", None, true, Some("teamX"))
25
- .unwrap();
26
- store.mark(&m_ack, "delivered", None).unwrap();
27
- store.mark(&m_ack, "acknowledged", None).unwrap();
28
- let m_vis = store
29
- .create_message(None, "leader", "w1", "vis", None, true, Some("teamX"))
30
- .unwrap();
31
- store.mark(&m_vis, "visible", None).unwrap();
32
- // NOT golden-delivered (must be EXCLUDED): injected.
33
- let m_inj = store
34
- .create_message(None, "leader", "w1", "inj", None, true, Some("teamX"))
35
- .unwrap();
36
- store.mark(&m_inj, "injected", None).unwrap();
37
- drop(store);
38
- let _ = stuck_cancel(&ws, "w1", None, "leader").unwrap();
39
- let state = crate::state::persist::load_runtime_state(&ws).unwrap();
40
- let snapshot =
41
- &state["coordinator"]["suppressed_idle_alerts"]["teamX"]["w1"]["stuck"]["snapshot"];
42
- assert!(
43
- snapshot.get("assigned_task_ids").is_some(),
44
- "F1: snapshot must carry assigned_task_ids; got {snapshot}"
45
- );
46
- let delivered = snapshot
47
- .get("delivered_message_ids")
48
- .and_then(serde_json::Value::as_array)
49
- .map(|a| {
50
- a.iter()
51
- .filter_map(|v| v.as_str().map(str::to_string))
52
- .collect::<Vec<_>>()
53
- })
54
- .unwrap_or_else(|| panic!("F1: snapshot must carry delivered_message_ids; got {snapshot}"));
55
- assert!(
56
- delivered.contains(&m_ack),
57
- "F1: golden _DELIVERED_MESSAGE_STATUSES includes 'acknowledged' — the acknowledged message must be \
58
- in delivered_message_ids; the Rust set omits it. got {delivered:?}"
59
- );
60
- assert!(
61
- delivered.contains(&m_vis),
62
- "F1: 'visible' is delivered (golden); got {delivered:?}"
63
- );
64
- assert!(
65
- !delivered.contains(&m_inj),
66
- "F1: 'injected' is NOT in golden _DELIVERED_MESSAGE_STATUSES — it must be EXCLUDED; the Rust set \
67
- wrongly includes it. got {delivered:?}"
68
- );
69
- let mut sorted = delivered.clone();
70
- sorted.sort();
71
- assert_eq!(
72
- delivered, sorted,
73
- "F1: delivered_message_ids must be sorted (golden sorts); got {delivered:?}"
74
- );
75
- }
76
3
  // ── collect --result-file INGEST SEMANTIC (golden results.py:58-73) ──────────────────────────────
77
4
  // Golden `collect(result_file=…)` INDEPENDENTLY INGESTS a standalone result: a valid result_envelope_v1
78
5
  // is `store.add_result(envelope)`'d (results.py:73) regardless of any in-flight delivery, then the