@team-agent/installer 0.5.64 → 0.5.66

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 (105) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +5 -0
  4. package/crates/team-agent/src/cli/adapters.rs +6 -1
  5. package/crates/team-agent/src/cli/emit.rs +15 -0
  6. package/crates/team-agent/src/cli/mod.rs +9 -8
  7. package/crates/team-agent/src/cli/send/presentation.rs +1 -0
  8. package/crates/team-agent/src/cli/spec.rs +3 -0
  9. package/crates/team-agent/src/cli/tests/compile.rs +1 -1
  10. package/crates/team-agent/src/cli/tests/mod.rs +33 -2
  11. package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -1
  12. package/crates/team-agent/src/cli/tests/status_send.rs +13 -3
  13. package/crates/team-agent/src/cli/tests/verb_validate.rs +1 -1
  14. package/crates/team-agent/src/cli/types.rs +7 -0
  15. package/crates/team-agent/src/compiler/tests.rs +23 -20
  16. package/crates/team-agent/src/compiler.rs +25 -5
  17. package/crates/team-agent/src/conpty/backend.rs +16 -0
  18. package/crates/team-agent/src/coordinator/health.rs +17 -0
  19. package/crates/team-agent/src/coordinator/steps/abnormal.rs +55 -2
  20. package/crates/team-agent/src/db/migration.rs +2 -1
  21. package/crates/team-agent/src/db/schema.rs +16 -8
  22. package/crates/team-agent/src/leader/lease.rs +1 -2
  23. package/crates/team-agent/src/leader/start.rs +175 -10
  24. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1 -1
  25. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +0 -2
  26. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +7 -2
  27. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +137 -2
  28. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +1 -2
  29. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +5 -0
  30. package/crates/team-agent/src/lifecycle/launch/spawn.rs +39 -2
  31. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +105 -48
  32. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +0 -2
  33. package/crates/team-agent/src/lifecycle/launch.rs +117 -21
  34. package/crates/team-agent/src/lifecycle/restart/agent.rs +89 -3
  35. package/crates/team-agent/src/lifecycle/restart/common.rs +9 -12
  36. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +59 -15
  37. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1 -1
  38. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +4 -4
  39. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +1 -1
  40. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -2
  41. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +4 -4
  42. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +24 -32
  43. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +2 -2
  44. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +13 -11
  45. package/crates/team-agent/src/lifecycle/tests/core.rs +0 -18
  46. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +1 -1
  47. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +1 -1
  48. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +1 -1
  49. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -0
  50. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +3 -3
  51. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +2 -2
  52. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +17 -30
  53. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +1 -1
  54. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +4 -4
  55. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +1 -1
  56. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +2 -2
  57. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +1 -1
  58. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +1 -1
  59. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1 -1
  60. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -2
  61. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +1 -1
  62. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +1 -1
  63. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +11 -7
  64. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +3 -3
  65. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1 -1
  66. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +1 -1
  67. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -1
  68. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1 -1
  69. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1 -1
  70. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +1 -1
  71. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +1 -1
  72. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -1
  73. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -2
  74. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +1 -1
  75. package/crates/team-agent/src/lifecycle/tests.rs +11 -7
  76. package/crates/team-agent/src/lifecycle/types.rs +0 -2
  77. package/crates/team-agent/src/lifecycle/worker_command_context.rs +115 -22
  78. package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
  79. package/crates/team-agent/src/messaging/delivery.rs +29 -0
  80. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  81. package/crates/team-agent/src/messaging/leader_receiver.rs +12 -0
  82. package/crates/team-agent/src/messaging/mod.rs +2 -0
  83. package/crates/team-agent/src/messaging/results.rs +5 -0
  84. package/crates/team-agent/src/messaging/send.rs +9 -0
  85. package/crates/team-agent/src/messaging/tests/main_preserved.rs +1 -1
  86. package/crates/team-agent/src/messaging/tests/runtime.rs +32 -0
  87. package/crates/team-agent/src/messaging/types.rs +2 -0
  88. package/crates/team-agent/src/messaging/wait.rs +366 -0
  89. package/crates/team-agent/src/messaging/watchers.rs +153 -6
  90. package/crates/team-agent/src/model/spec.rs +129 -6
  91. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +3 -1
  92. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +3 -1
  93. package/crates/team-agent/src/model/testdata/team.spec.yaml +3 -1
  94. package/crates/team-agent/src/provider/adapters/claude.rs +5 -2
  95. package/crates/team-agent/src/provider/adapters/codex.rs +5 -2
  96. package/crates/team-agent/src/provider/adapters/copilot.rs +5 -2
  97. package/crates/team-agent/src/provider/bypass_flags.rs +80 -0
  98. package/crates/team-agent/src/provider/mod.rs +2 -0
  99. package/crates/team-agent/src/tmux_backend.rs +35 -12
  100. package/crates/team-agent/src/transport/test_support.rs +20 -0
  101. package/crates/team-agent/src/transport.rs +28 -11
  102. package/package.json +4 -4
  103. package/crates/team-agent/src/lifecycle/launch/approval.rs +0 -134
  104. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +0 -1187
  105. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +0 -327
@@ -24,17 +24,23 @@ fn sess(s: &str) -> SessionName {
24
24
  pub(crate) fn test_binary_path() -> &'static str {
25
25
  static PATH: OnceLock<String> = OnceLock::new();
26
26
  PATH.get_or_init(|| {
27
- if let Ok(path) = std::env::var("CARGO_BIN_EXE_team-agent") {
28
- return path;
29
- }
30
- let current = std::env::current_exe().expect("test executable path");
31
- current
27
+ let path = if let Ok(path) = std::env::var("CARGO_BIN_EXE_team-agent") {
28
+ path
29
+ } else {
30
+ let current = std::env::current_exe().expect("test executable path");
31
+ current
32
32
  .parent()
33
33
  .and_then(|deps| deps.parent())
34
34
  .map(|target| target.join("team-agent"))
35
35
  .expect("team-agent test binary path")
36
36
  .to_string_lossy()
37
37
  .into_owned()
38
+ };
39
+ assert!(
40
+ std::path::Path::new(&path).is_file(),
41
+ "team-agent test binary does not exist: {path}; run `cargo build -p team-agent --bin team-agent` first"
42
+ );
43
+ path
38
44
  })
39
45
  .as_str()
40
46
  }
@@ -52,7 +58,6 @@ mod b0_legacy_snapshot_nonauthority_contract;
52
58
  mod b0_reader_hideone_audit_contract;
53
59
  mod behavioral_diff_264_red;
54
60
  mod claude_profile_launch_red;
55
- mod codex_mcp_approval_inheritance_red;
56
61
  mod codex_weak_window_attribution_red;
57
62
  mod copilot_provider_red;
58
63
  mod display_adaptive_red;
@@ -82,7 +87,6 @@ mod team_key_retirement_txn_red;
82
87
  mod test_isolation_escape_contract;
83
88
  mod upgrade_compat_0211_red;
84
89
  mod verify_rs031_window_consistency_red;
85
- mod worker_dangerous_bypass_argv_red;
86
90
  mod worker_spawn_env_red;
87
91
  mod acceptance_b_batch_red;
88
92
  mod claude_compatible_config_red;
@@ -473,8 +473,6 @@ pub struct LaunchReport {
473
473
  pub routes: Vec<RoutingDecision>,
474
474
  /// 权限摘要(每 agent 一条)。
475
475
  pub permissions: Vec<PermissionSummary>,
476
- /// 危险审批安全态(dry-run 报告里的 `safety`)。
477
- pub safety: DangerousApproval,
478
476
  /// leader receiver(attach 成功时;经 step10 leader::attach_leader_to_state)。
479
477
  pub leader_receiver_attached: bool,
480
478
  pub session_capture_incomplete_agents: Vec<String>,
@@ -1,7 +1,7 @@
1
1
  use std::path::Path;
2
2
 
3
3
  use crate::communication_mode::CommunicationMode;
4
- use crate::lifecycle::types::{DangerousApproval, LifecycleError};
4
+ use crate::lifecycle::types::LifecycleError;
5
5
  use crate::model::enums::{Enforcement, Provider};
6
6
  use crate::model::ids::AgentId;
7
7
  use crate::model::permissions::{resolve_permissions, AgentPermissionInput};
@@ -42,6 +42,8 @@ pub(crate) struct WorkerCommandAgent {
42
42
  system_prompt_file: Option<String>,
43
43
  output_contract_format: Option<String>,
44
44
  communication_mode: CommunicationMode,
45
+ /// 0.5.66 bypass 单源:agent-level `dangerously_skip_permissions`(必填 bool)。
46
+ dangerously_skip_permissions: bool,
45
47
  }
46
48
 
47
49
  impl WorkerCommandAgent {
@@ -92,6 +94,10 @@ impl WorkerCommandAgent {
92
94
  .get("communication_mode")
93
95
  .and_then(crate::model::yaml::Value::as_str),
94
96
  )?,
97
+ dangerously_skip_permissions: matches!(
98
+ agent.get("dangerously_skip_permissions"),
99
+ Some(crate::model::yaml::Value::Bool(true))
100
+ ),
95
101
  })
96
102
  }
97
103
 
@@ -142,6 +148,10 @@ impl WorkerCommandAgent {
142
148
  .get("communication_mode")
143
149
  .and_then(serde_json::Value::as_str),
144
150
  )?,
151
+ dangerously_skip_permissions: agent
152
+ .get("dangerously_skip_permissions")
153
+ .and_then(serde_json::Value::as_bool)
154
+ .unwrap_or(false),
145
155
  })
146
156
  }
147
157
  }
@@ -175,19 +185,44 @@ pub(crate) fn compile_worker_system_prompt(
175
185
  pub(crate) fn resolved_tool_strings_for_command(
176
186
  agent: &WorkerCommandAgent,
177
187
  provider: Provider,
178
- safety: &DangerousApproval,
179
188
  ) -> Result<Vec<String>, LifecycleError> {
180
189
  let mut tools: Vec<String> = resolve_agent_permissions(agent, provider)?
181
190
  .sorted_tool_strings()
182
191
  .into_iter()
183
192
  .map(str::to_string)
184
193
  .collect();
185
- if safety.enabled && !tools.iter().any(|tool| tool == "dangerous_auto_approve") {
186
- tools.push("dangerous_auto_approve".to_string());
194
+ // 0.5.66 bypass 单源:读 agent 自己的 `dangerously_skip_permissions` bool,
195
+ // 不再收 team/runtime/leader argv 派生的 `DangerousApproval`。true → 加
196
+ // `dangerous_auto_approve` 哨兵(adapter 消费点不变);provider 无 bypass
197
+ // argv 定义 → fail-loud,不静默 fallback。
198
+ if agent.dangerously_skip_permissions {
199
+ let flag = crate::provider::bypass_flags::provider_bypass_flag(provider).ok_or_else(
200
+ || {
201
+ LifecycleError::RequirementUnmet(format!(
202
+ "provider {} has no bypass argv flag defined; dangerously_skip_permissions=true cannot be honored",
203
+ provider_display_name(provider)
204
+ ))
205
+ },
206
+ )?;
207
+ debug_assert!(flag.starts_with("--"));
208
+ if !tools.iter().any(|tool| tool == "dangerous_auto_approve") {
209
+ tools.push("dangerous_auto_approve".to_string());
210
+ }
187
211
  }
188
212
  Ok(tools)
189
213
  }
190
214
 
215
+ fn provider_display_name(provider: Provider) -> &'static str {
216
+ match provider {
217
+ Provider::Claude => "claude",
218
+ Provider::ClaudeCode => "claude_code",
219
+ Provider::Codex => "codex",
220
+ Provider::Copilot => "copilot",
221
+ Provider::GeminiCli => "gemini_cli",
222
+ Provider::Fake => "fake",
223
+ }
224
+ }
225
+
191
226
  fn resolve_agent_permissions(
192
227
  agent: &WorkerCommandAgent,
193
228
  provider: Provider,
@@ -282,20 +317,6 @@ fn provider_default_prompt_only_tools(provider: Provider) -> &'static [&'static
282
317
  #[cfg(test)]
283
318
  mod tests {
284
319
  use super::*;
285
- use crate::lifecycle::types::DangerousApprovalSource;
286
-
287
- fn disabled_safety() -> DangerousApproval {
288
- DangerousApproval {
289
- enabled: false,
290
- source: DangerousApprovalSource::Disabled,
291
- inherited: false,
292
- provider: None,
293
- flag: None,
294
- worker_capability_above_leader: false,
295
- ancestry_binary_name: None,
296
- unexpected_binary: false,
297
- }
298
- }
299
320
 
300
321
  #[test]
301
322
  fn empty_tools_use_role_defaults_and_aliases_resolve_before_command() {
@@ -308,10 +329,10 @@ mod tests {
308
329
  system_prompt_file: None,
309
330
  output_contract_format: None,
310
331
  communication_mode: CommunicationMode::default(),
332
+ dangerously_skip_permissions: false,
311
333
  };
312
334
  let tools =
313
- resolved_tool_strings_for_command(&agent, Provider::ClaudeCode, &disabled_safety())
314
- .unwrap();
335
+ resolved_tool_strings_for_command(&agent, Provider::ClaudeCode).unwrap();
315
336
  assert_eq!(
316
337
  tools,
317
338
  [
@@ -330,11 +351,82 @@ mod tests {
330
351
  ..agent
331
352
  };
332
353
  let tools =
333
- resolved_tool_strings_for_command(&agent, Provider::ClaudeCode, &disabled_safety())
334
- .unwrap();
354
+ resolved_tool_strings_for_command(&agent, Provider::ClaudeCode).unwrap();
335
355
  assert_eq!(tools, ["fs_list", "fs_read", "fs_write", "mcp_team"]);
336
356
  }
337
357
 
358
+ // 0.5.66 bypass 单源 §4.1:true → tools 追加 `dangerous_auto_approve` 哨兵。
359
+ #[test]
360
+ fn test_true_field_produces_bypass_flag_argv() {
361
+ let agent = WorkerCommandAgent {
362
+ id: Some("dev".to_string()),
363
+ provider: Provider::ClaudeCode,
364
+ role: Some("developer".to_string()),
365
+ declared_tools: Some(vec!["mcp_team".to_string()]),
366
+ system_prompt_inline: None,
367
+ system_prompt_file: None,
368
+ output_contract_format: None,
369
+ communication_mode: CommunicationMode::default(),
370
+ dangerously_skip_permissions: true,
371
+ };
372
+ let tools = resolved_tool_strings_for_command(&agent, Provider::ClaudeCode).unwrap();
373
+ assert!(
374
+ tools.iter().any(|tool| tool == "dangerous_auto_approve"),
375
+ "true field must append the dangerous_auto_approve sentinel; tools={tools:?}"
376
+ );
377
+ assert!(
378
+ tools.iter().any(|tool| tool == "mcp_team"),
379
+ "normal tools must remain; tools={tools:?}"
380
+ );
381
+ }
382
+
383
+ // 0.5.66 bypass 单源 §4.1:false → 不追加哨兵。
384
+ #[test]
385
+ fn test_false_field_produces_no_bypass_flag_argv() {
386
+ let agent = WorkerCommandAgent {
387
+ id: Some("dev".to_string()),
388
+ provider: Provider::ClaudeCode,
389
+ role: Some("developer".to_string()),
390
+ declared_tools: Some(vec!["mcp_team".to_string()]),
391
+ system_prompt_inline: None,
392
+ system_prompt_file: None,
393
+ output_contract_format: None,
394
+ communication_mode: CommunicationMode::default(),
395
+ dangerously_skip_permissions: false,
396
+ };
397
+ let tools = resolved_tool_strings_for_command(&agent, Provider::ClaudeCode).unwrap();
398
+ assert!(
399
+ !tools.iter().any(|tool| tool == "dangerous_auto_approve"),
400
+ "false field must not append the sentinel; tools={tools:?}"
401
+ );
402
+ }
403
+
404
+ // 0.5.66 bypass 单源 §4.1:provider 未定义 bypass 参数 + true → fail-loud。
405
+ #[test]
406
+ fn test_provider_without_bypass_flag_definition_errors() {
407
+ let agent = WorkerCommandAgent {
408
+ id: Some("dev".to_string()),
409
+ provider: Provider::GeminiCli,
410
+ role: Some("developer".to_string()),
411
+ declared_tools: Some(vec!["mcp_team".to_string()]),
412
+ system_prompt_inline: None,
413
+ system_prompt_file: None,
414
+ output_contract_format: None,
415
+ communication_mode: CommunicationMode::default(),
416
+ dangerously_skip_permissions: true,
417
+ };
418
+ let err = resolved_tool_strings_for_command(&agent, Provider::GeminiCli).unwrap_err();
419
+ let msg = err.to_string();
420
+ assert!(
421
+ msg.contains("no bypass argv flag defined"),
422
+ "provider without bypass flag + true must fail-loud; got {msg}"
423
+ );
424
+ assert!(
425
+ msg.contains("gemini_cli"),
426
+ "error must name the provider; got {msg}"
427
+ );
428
+ }
429
+
338
430
  #[test]
339
431
  fn system_prompt_uses_identity_then_runtime_contract_python_order() {
340
432
  // #264 D6: Python truth source (prompt.py:39, live ps confirmed) builds
@@ -353,6 +445,7 @@ mod tests {
353
445
  system_prompt_file: None,
354
446
  output_contract_format: Some("result_envelope_v1".to_string()),
355
447
  communication_mode: CommunicationMode::default(),
448
+ dangerously_skip_permissions: false,
356
449
  };
357
450
  let prompt = compile_worker_system_prompt(&agent).unwrap();
358
451
  assert!(
@@ -211,6 +211,7 @@ pub(crate) fn delivery_outcome_value(out: &DeliveryOutcome) -> Value {
211
211
  "ok": out.ok,
212
212
  "status": enum_value(out.status),
213
213
  "message_id": out.message_id,
214
+ "ack_forced_off": out.ack_forced_off,
214
215
  });
215
216
  if let Some(obj) = value.as_object_mut() {
216
217
  if let Some(reason) = out.reason {
@@ -74,6 +74,7 @@ pub fn deliver_stored_message(
74
74
  stage: None,
75
75
  reason: None,
76
76
  channel: None,
77
+ ack_forced_off: false,
77
78
  })
78
79
  }
79
80
 
@@ -181,6 +182,7 @@ pub fn deliver_pending_message(
181
182
  stage: None,
182
183
  reason: None,
183
184
  channel: None,
185
+ ack_forced_off: false,
184
186
  });
185
187
  }
186
188
  let message = message_for_delivery(store, message_id)?;
@@ -194,6 +196,7 @@ pub fn deliver_pending_message(
194
196
  stage: None,
195
197
  reason: Some(DeliveryRefusal::UnknownRecipient),
196
198
  channel: None,
199
+ ack_forced_off: false,
197
200
  });
198
201
  };
199
202
  let mut canonical_owner_team_id = message.owner_team_id.clone();
@@ -281,6 +284,7 @@ pub fn deliver_pending_message(
281
284
  stage: None,
282
285
  reason: Some(DeliveryRefusal::MessageAlreadyClaimed),
283
286
  channel: None,
287
+ ack_forced_off: false,
284
288
  });
285
289
  };
286
290
  if message.recipient == "leader" {
@@ -310,6 +314,7 @@ pub fn deliver_pending_message(
310
314
  stage: None,
311
315
  reason: Some(DeliveryRefusal::LeaderNotAttached),
312
316
  channel: Some("rebind_required".to_string()),
317
+ ack_forced_off: false,
313
318
  });
314
319
  };
315
320
  if let Some(outcome) = leader_receiver_transport_conflict_outcome(
@@ -340,6 +345,7 @@ pub fn deliver_pending_message(
340
345
  stage: None,
341
346
  reason: None,
342
347
  channel: Some("rebind_required".to_string()),
348
+ ack_forced_off: false,
343
349
  });
344
350
  }
345
351
  crate::messaging::LeaderChannelResolution::Unbound(reason) => {
@@ -378,6 +384,7 @@ pub fn deliver_pending_message(
378
384
  stage: None,
379
385
  reason: Some(refusal),
380
386
  channel: Some("rebind_required".to_string()),
387
+ ack_forced_off: false,
381
388
  });
382
389
  }
383
390
  }
@@ -424,6 +431,7 @@ pub fn deliver_pending_message(
424
431
  stage: None,
425
432
  reason: None,
426
433
  channel: None,
434
+ ack_forced_off: false,
427
435
  });
428
436
  }
429
437
  };
@@ -471,6 +479,7 @@ pub fn deliver_pending_message(
471
479
  stage: Some(DeliveryStage::TrustAutoAnswerDismissalWait),
472
480
  reason: None,
473
481
  channel: None,
482
+ ack_forced_off: false,
474
483
  });
475
484
  }
476
485
  let rendered = render_message(
@@ -528,6 +537,7 @@ pub fn deliver_pending_message(
528
537
  stage: None,
529
538
  reason: Some(DeliveryRefusal::LeaderNotAttached),
530
539
  channel: Some("rebind_required".to_string()),
540
+ ack_forced_off: false,
531
541
  });
532
542
  }
533
543
  if transport_error_is_target_missing(&error) {
@@ -571,6 +581,7 @@ pub fn deliver_pending_message(
571
581
  stage: Some(DeliveryStage::Inject),
572
582
  reason: None,
573
583
  channel: None,
584
+ ack_forced_off: false,
574
585
  });
575
586
  }
576
587
  store.mark(message_id, "target_resolved", Some(&reason))?;
@@ -583,6 +594,7 @@ pub fn deliver_pending_message(
583
594
  stage: Some(DeliveryStage::Inject),
584
595
  reason: None,
585
596
  channel: None,
597
+ ack_forced_off: false,
586
598
  });
587
599
  }
588
600
  };
@@ -642,6 +654,7 @@ pub fn deliver_pending_message(
642
654
  stage: Some(DeliveryStage::Submit),
643
655
  reason: None,
644
656
  channel: None,
657
+ ack_forced_off: false,
645
658
  });
646
659
  }
647
660
  store.mark(message_id, "submitted_unverified", Some(&reason))?;
@@ -654,6 +667,7 @@ pub fn deliver_pending_message(
654
667
  stage: Some(DeliveryStage::Submit),
655
668
  reason: None,
656
669
  channel: None,
670
+ ack_forced_off: false,
657
671
  });
658
672
  }
659
673
  // S1-CAPTURE-001 (0.4.8, CR M4 Claude phase-1): for Claude/ClaudeCode
@@ -718,6 +732,7 @@ pub fn deliver_pending_message(
718
732
  stage: Some(DeliveryStage::Submit),
719
733
  reason: None,
720
734
  channel: None,
735
+ ack_forced_off: false,
721
736
  });
722
737
  }
723
738
  }
@@ -766,6 +781,7 @@ pub fn deliver_pending_message(
766
781
  stage: None,
767
782
  reason: None,
768
783
  channel: None,
784
+ ack_forced_off: false,
769
785
  };
770
786
  stamp_first_send_at_if_leader_to_worker_scoped(
771
787
  workspace,
@@ -847,6 +863,7 @@ pub(crate) fn mark_worker_target_missing(
847
863
  stage: Some(DeliveryStage::Inject),
848
864
  reason: Some(DeliveryRefusal::TmuxTargetMissing),
849
865
  channel: Some("delivery_blocked".to_string()),
866
+ ack_forced_off: false,
850
867
  })
851
868
  }
852
869
 
@@ -1000,6 +1017,7 @@ fn deliver_leader_via_app_server(
1000
1017
  stage: None,
1001
1018
  reason: None,
1002
1019
  channel: Some("codex_app_server".to_string()),
1020
+ ack_forced_off: false,
1003
1021
  })
1004
1022
  }
1005
1023
  Err(error) => app_server_delivery_failure(
@@ -1045,6 +1063,7 @@ fn app_server_delivery_failure(
1045
1063
  stage: None,
1046
1064
  reason: Some(DeliveryRefusal::RecipientBusy),
1047
1065
  channel: Some("leader_busy".to_string()),
1066
+ ack_forced_off: false,
1048
1067
  })
1049
1068
  }
1050
1069
  crate::codex_app_server::AppServerError::ThreadStale { expected, actual } => {
@@ -1083,6 +1102,7 @@ fn app_server_delivery_failure(
1083
1102
  stage: None,
1084
1103
  reason: Some(DeliveryRefusal::MissingPermissions),
1085
1104
  channel: Some("codex_app_server".to_string()),
1105
+ ack_forced_off: false,
1086
1106
  })
1087
1107
  }
1088
1108
  crate::codex_app_server::AppServerError::ProtocolMismatch(_)
@@ -1134,6 +1154,7 @@ fn rebind_required_outcome(message_id: &str, action: &str) -> DeliveryOutcome {
1134
1154
  stage: None,
1135
1155
  reason: Some(DeliveryRefusal::LeaderNotAttached),
1136
1156
  channel: Some("rebind_required".to_string()),
1157
+ ack_forced_off: false,
1137
1158
  }
1138
1159
  }
1139
1160
 
@@ -1536,6 +1557,7 @@ fn leader_receiver_transport_conflict_outcome(
1536
1557
  stage: None,
1537
1558
  reason: Some(DeliveryRefusal::LeaderNotAttached),
1538
1559
  channel: Some("rebind_required".to_string()),
1560
+ ack_forced_off: false,
1539
1561
  }));
1540
1562
  }
1541
1563
  }
@@ -2106,6 +2128,7 @@ fn leader_acceptance_pending_outcome(message_id: &str, status: &str) -> Delivery
2106
2128
  stage: Some(DeliveryStage::Submit),
2107
2129
  reason: None,
2108
2130
  channel: Some("leader_acceptance_pending".to_string()),
2131
+ ack_forced_off: false,
2109
2132
  }
2110
2133
  }
2111
2134
 
@@ -2119,6 +2142,7 @@ pub(crate) fn leader_receipt_source_unavailable_outcome(message_id: &str) -> Del
2119
2142
  stage: Some(DeliveryStage::Submit),
2120
2143
  reason: None,
2121
2144
  channel: Some("leader_receipt_source_unavailable".to_string()),
2145
+ ack_forced_off: false,
2122
2146
  }
2123
2147
  }
2124
2148
 
@@ -2159,6 +2183,7 @@ fn observe_pending_leader_acceptance(
2159
2183
  stage: None,
2160
2184
  reason: None,
2161
2185
  channel: Some("leader_receiver".to_string()),
2186
+ ack_forced_off: false,
2162
2187
  })
2163
2188
  }
2164
2189
 
@@ -2188,6 +2213,7 @@ pub fn handle_trust_retry_needed(
2188
2213
  stage: Some(DeliveryStage::TrustAutoAnswerDismissalWait),
2189
2214
  reason: None,
2190
2215
  channel: None,
2216
+ ack_forced_off: false,
2191
2217
  });
2192
2218
  }
2193
2219
  let next_attempt = payload.attempt.saturating_add(1);
@@ -2227,6 +2253,7 @@ pub fn handle_trust_retry_needed(
2227
2253
  stage: Some(DeliveryStage::TrustAutoAnswerDismissalWait),
2228
2254
  reason: None,
2229
2255
  channel: None,
2256
+ ack_forced_off: false,
2230
2257
  })
2231
2258
  }
2232
2259
 
@@ -2723,6 +2750,7 @@ fn refuse_owner_team_resolution(
2723
2750
  stage: None,
2724
2751
  reason: Some(refusal),
2725
2752
  channel: Some("owner_team_resolution".to_string()),
2753
+ ack_forced_off: false,
2726
2754
  })
2727
2755
  }
2728
2756
 
@@ -2779,6 +2807,7 @@ pub fn retry_injection_after_trust_auto_answer(
2779
2807
  stage: Some(DeliveryStage::TrustAutoAnswerDismissalWait),
2780
2808
  reason: None,
2781
2809
  channel: None,
2810
+ ack_forced_off: false,
2782
2811
  })
2783
2812
  }
2784
2813
 
@@ -294,6 +294,7 @@ pub(crate) fn fail_leader_delivery(
294
294
  stage: None,
295
295
  reason: Some(reason),
296
296
  channel: Some("fallback_log".to_string()),
297
+ ack_forced_off: false,
297
298
  });
298
299
  };
299
300
  store.mark(&message_id, "failed", error)?;
@@ -310,6 +311,7 @@ pub(crate) fn fail_leader_delivery(
310
311
  stage: None,
311
312
  reason: Some(reason),
312
313
  channel: Some("fallback_inbox".to_string()),
314
+ ack_forced_off: false,
313
315
  })
314
316
  }
315
317
 
@@ -133,6 +133,7 @@ pub fn send_to_leader_receiver_with_presentation(
133
133
  stage: None,
134
134
  reason: Some(DeliveryRefusal::Duplicate),
135
135
  channel: Some("leader_receiver".to_string()),
136
+ ack_forced_off: requires_ack,
136
137
  });
137
138
  }
138
139
  PersistResolution::Persisted(persisted) => persisted.message_id,
@@ -174,6 +175,7 @@ pub fn send_to_leader_receiver_with_presentation(
174
175
  stage: None,
175
176
  reason: None,
176
177
  channel: Some("leader_receiver".to_string()),
178
+ ack_forced_off: requires_ack,
177
179
  });
178
180
  }
179
181
  }
@@ -210,6 +212,7 @@ pub fn send_to_leader_receiver_with_presentation(
210
212
  stage: None,
211
213
  reason: None,
212
214
  channel: Some("leader_receiver".to_string()),
215
+ ack_forced_off: requires_ack,
213
216
  })
214
217
  }
215
218
 
@@ -255,6 +258,7 @@ pub fn deliver_to_leader_fallback_pane(
255
258
  stage: None,
256
259
  reason: None,
257
260
  channel: Some("leader_receiver".to_string()),
261
+ ack_forced_off: false,
258
262
  });
259
263
  }
260
264
 
@@ -269,6 +273,7 @@ pub fn deliver_to_leader_fallback_pane(
269
273
  stage: None,
270
274
  reason: None,
271
275
  channel: Some("leader_receiver".to_string()),
276
+ ack_forced_off: false,
272
277
  });
273
278
  }
274
279
 
@@ -310,6 +315,7 @@ pub fn deliver_to_leader_fallback_pane(
310
315
  stage: None,
311
316
  reason: Some(DeliveryRefusal::LeaderNotAttached),
312
317
  channel: Some("fallback_pane".to_string()),
318
+ ack_forced_off: false,
313
319
  });
314
320
  };
315
321
 
@@ -383,6 +389,7 @@ pub fn deliver_to_leader_fallback_pane(
383
389
  stage: None,
384
390
  reason: Some(DeliveryRefusal::LeaderNotAttached),
385
391
  channel: Some("rebind_required".to_string()),
392
+ ack_forced_off: false,
386
393
  });
387
394
  };
388
395
  let target = Target::Pane(PaneId::new(channel.pane_id));
@@ -435,6 +442,7 @@ pub fn deliver_to_leader_fallback_pane(
435
442
  stage: Some(DeliveryStage::Submit),
436
443
  reason: None,
437
444
  channel: Some("leader_acceptance_pending".to_string()),
445
+ ack_forced_off: false,
438
446
  });
439
447
  }
440
448
  super::delivery::LeaderReceiptObservation::TokenObserved => {}
@@ -461,6 +469,7 @@ pub fn deliver_to_leader_fallback_pane(
461
469
  stage: None,
462
470
  reason: None,
463
471
  channel: Some("fallback_pane".to_string()),
472
+ ack_forced_off: false,
464
473
  })
465
474
  } else {
466
475
  let reason = format!(
@@ -486,6 +495,7 @@ pub fn deliver_to_leader_fallback_pane(
486
495
  stage: Some(DeliveryStage::Submit),
487
496
  reason: None,
488
497
  channel: Some("fallback_pane".to_string()),
498
+ ack_forced_off: false,
489
499
  })
490
500
  }
491
501
  }
@@ -527,6 +537,7 @@ pub fn deliver_to_leader_fallback_pane(
527
537
  stage: None,
528
538
  reason: Some(DeliveryRefusal::TmuxTargetMissing),
529
539
  channel: Some(channel),
540
+ ack_forced_off: false,
530
541
  })
531
542
  }
532
543
  }
@@ -718,5 +729,6 @@ pub fn enqueue_leader_mailbox_until_attach(
718
729
  stage: None,
719
730
  reason: None,
720
731
  channel: Some("leader_mailbox".to_string()),
732
+ ack_forced_off: false,
721
733
  })
722
734
  }
@@ -76,6 +76,7 @@ pub mod selftest;
76
76
  pub mod send;
77
77
  pub mod trust;
78
78
  pub mod types;
79
+ pub mod wait;
79
80
  pub mod watchers;
80
81
 
81
82
  // ── re-export: 保持 `crate::messaging::X` 与 test `super::X` 解析不变 ──────────
@@ -118,6 +119,7 @@ pub use types::{
118
119
  TrustRetryPayload, WatcherNotice, WorkerRuntimeState, RESULT_DELIVERY_MAX_ATTEMPTS,
119
120
  SEND_RETRY_MAX_ATTEMPTS, TRUST_RETRY_BACKOFF_SECONDS, TRUST_RETRY_MAX_ATTEMPTS,
120
121
  };
122
+ pub use wait::{wait_for_result, WaitResult};
121
123
  pub use watchers::{
122
124
  format_result_watcher_notification, requeue_after_claim_leader,
123
125
  requeue_delivery_exhausted_watchers, result_id_from_text, retry_result_deliveries,
@@ -842,6 +842,7 @@ fn report_result_for_owner_team_inner(
842
842
  return Ok(serde_json::Value::Object(out));
843
843
  }
844
844
  let event_log = EventLog::new(workspace);
845
+ super::watchers::notify_fifo_result_watchers(&conn, &event_log, task_id, &result_id)?;
845
846
  if presentation.effective_sink != super::presentation::PresentationSink::Leader {
846
847
  event_log.write(
847
848
  "presentation.stored_without_live_inject",
@@ -938,6 +939,7 @@ fn report_result_for_owner_team_inner(
938
939
  stage: None,
939
940
  reason: Some(crate::messaging::DeliveryRefusal::LeaderNotAttached),
940
941
  channel: Some("codex_app_server".to_string()),
942
+ ack_forced_off: false,
941
943
  }
942
944
  }
943
945
  Err(error) => {
@@ -1002,6 +1004,7 @@ fn report_result_for_owner_team_inner(
1002
1004
  stage: None,
1003
1005
  reason: None,
1004
1006
  channel: Some("leader_receiver".to_string()),
1007
+ ack_forced_off: false,
1005
1008
  };
1006
1009
  break;
1007
1010
  }
@@ -1031,6 +1034,7 @@ fn report_result_for_owner_team_inner(
1031
1034
  stage: None,
1032
1035
  reason: None,
1033
1036
  channel: Some("leader_receiver".to_string()),
1037
+ ack_forced_off: false,
1034
1038
  };
1035
1039
  break;
1036
1040
  }
@@ -1046,6 +1050,7 @@ fn report_result_for_owner_team_inner(
1046
1050
  stage: None,
1047
1051
  reason: None,
1048
1052
  channel: Some("leader_receiver".to_string()),
1053
+ ack_forced_off: false,
1049
1054
  };
1050
1055
  break;
1051
1056
  }