@team-agent/installer 0.5.49 → 0.5.50

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.
@@ -153,7 +153,7 @@ pub(crate) struct CommandSpec {
153
153
  #[rustfmt::skip]
154
154
  pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
155
155
  CommandSpec { name: "quick-start", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::QuickStart), summary: "start or attach a team from TEAM.md", usage: "usage: team-agent quick-start [TEAMDIR] [--workspace WORKSPACE] [--name NAME] [--team-id TEAM|--team TEAM] [--yes] [--no-display] [--backend tmux|conpty] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: None, action: None, governance: None },
156
- CommandSpec { name: "send", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Send), summary: "send a message/task", usage: "usage: team-agent send TARGET MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--targets AGENTS] [--to-name NAME | --to-name agent | --to-name team/agent | --to-name workspace::team/agent] [--pane PANE] [--task TASK] [--sender SENDER] [--watch-result] [--requires-ack|--no-ack] [--no-wait] [--timeout SECONDS] [--confirm-human] [--message-id ID] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: None, action: None, governance: None },
156
+ CommandSpec { name: "send", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Send), summary: "persist a message for a logical recipient", usage: "usage: team-agent send TO MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: Some("next compatibility release"), action: Some("use positional logical TO and the returned message id"), governance: None },
157
157
  CommandSpec { name: "status", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Status), summary: "show current team status", usage: "usage: team-agent status [AGENT] [--workspace WORKSPACE] [--team TEAM] [--summary|--json] [--detail]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
158
158
  CommandSpec { name: "collect", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Collect), summary: "collect reported results", usage: "usage: team-agent collect [--workspace WORKSPACE] [--team TEAM] [--result-file FILE] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
159
159
  CommandSpec { name: "restart", tier: CommandTier::Core, category: CommandCategory::TeamLifecycle, kind: CommandKind::Dispatch(DispatchKind::Restart), summary: "restart the selected team", usage: "usage: team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--session-converge-deadline SECONDS] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: None, action: None, governance: None },
@@ -41,7 +41,7 @@ fn cli_send_persists_real_message_row() {
41
41
  workspace: ws.clone(),
42
42
  team: None,
43
43
  task: None,
44
- sender: "leader".to_string(),
44
+ sender: TrustedSender::leader(),
45
45
  no_ack: false,
46
46
  no_wait: true,
47
47
  watch_result: false,
@@ -149,7 +149,7 @@ fn named_send_args(
149
149
  workspace: ws.to_path_buf(),
150
150
  team: None,
151
151
  task: None,
152
- sender: "leader".to_string(),
152
+ sender: TrustedSender::leader(),
153
153
  no_ack: false,
154
154
  no_wait: false,
155
155
  watch_result: false,
@@ -459,7 +459,7 @@ fn resolve_app_server_leader_name_fails_closed_on_transport_conflict() {
459
459
 
460
460
  #[cfg(unix)]
461
461
  #[test]
462
- fn send_to_name_app_server_leader_submits_turn_without_tmux_pane() {
462
+ fn send_to_name_app_server_leader_persists_before_async_delivery() {
463
463
  let ws = named_ws("appserver-leader-send");
464
464
  let fake = crate::app_server_test_support::FakeAppServer::start(
465
465
  "named-send",
@@ -484,11 +484,13 @@ fn send_to_name_app_server_leader_submits_turn_without_tmux_pane() {
484
484
  };
485
485
 
486
486
  assert_eq!(value["ok"], json!(true));
487
- assert_eq!(value["transport_kind"], json!("codex_app_server"));
488
- assert_eq!(fake.received_turns().len(), 1);
489
- assert_eq!(
490
- fake.received_turns()[0]["params"]["clientUserMessageId"],
491
- value["message_id"]
487
+ let message_id = value["message_id"].as_str().expect("persisted message id");
488
+ assert!(message_id.starts_with("msg_"));
489
+ let store = crate::message_store::MessageStore::open(&ws).unwrap();
490
+ assert!(store.message_exists(message_id).unwrap());
491
+ assert!(
492
+ fake.received_turns().is_empty(),
493
+ "send returns after persistence; coordinator owns physical delivery"
492
494
  );
493
495
  let _ = std::fs::remove_dir_all(&ws);
494
496
  }
@@ -90,7 +90,7 @@ fn run_dispatches_status_to_handler_returns_ok() {
90
90
 
91
91
  #[test]
92
92
  fn run_dispatches_send_to_handler_returns_ok() {
93
- // run([send w1 hello --sender leader --workspace <seeded>]) -> cmd_send -> messaging::send_message
93
+ // run([send w1 hello --workspace <seeded>]) -> cmd_send -> messaging::send_message
94
94
  // -> ok (w1 is an in-team agent) -> ExitCode::Ok. w1 must be seeded: golden refuses non-team targets.
95
95
  //
96
96
  // OLD seed: flat `{"agents": {"w1": ...}}`.
@@ -112,8 +112,6 @@ fn run_dispatches_send_to_handler_returns_ok() {
112
112
  "send".to_string(),
113
113
  "w1".to_string(),
114
114
  "hello".to_string(),
115
- "--sender".to_string(),
116
- "leader".to_string(),
117
115
  "--workspace".to_string(),
118
116
  ws.to_string_lossy().to_string(),
119
117
  ];
@@ -460,7 +460,7 @@ fn send_args_fixture() -> SendArgs {
460
460
  workspace: PathBuf::from("."),
461
461
  team: Some("teamA".into()),
462
462
  task: Some("t-1".into()),
463
- sender: "leader".into(),
463
+ sender: TrustedSender::leader(),
464
464
  no_ack: true,
465
465
  no_wait: true,
466
466
  watch_result: true,
@@ -515,7 +515,7 @@ fn send_options_negates_no_ack_and_no_wait_and_carries_watch() {
515
515
  "watch_result flag MUST pass through into SendOptions"
516
516
  );
517
517
  assert!(!opts.confirm_human);
518
- assert_eq!(opts.sender, "leader");
518
+ assert_eq!(opts.sender.as_str(), "leader");
519
519
  assert_eq!(opts.timeout, 12.5);
520
520
  }
521
521
 
@@ -866,15 +866,8 @@ fn cmd_send_unknown_task_surfaces_golden_error_envelope_not_silent() {
866
866
  let _ = std::fs::remove_dir_all(&ws);
867
867
  }
868
868
 
869
- // P0 (b') — the SWALLOW guard: `run()` (the CLI process entry) MUST RENDER the send error, not
870
- // discard Err(CliError) via unwrap_or (advisor %7 root cause). Proxy: emit_cli_error WRITES a
871
- // `.team/logs/cli-error-*.log` (and prints the compact envelope) — if run() swallowed, neither
872
- // happens. So a cli-error log containing the BARE "unknown task id: <id>" (no "validation:" prefix)
873
- // + ExitCode::Error proves run() rendered. Drives the real argv→(exit,render) path.
874
- // OLD/NEW: same Bug 1/2 seed sync as cmd_send_unknown_task_*; the render-vs-swallow
875
- // behavior under test is unchanged.
876
869
  #[test]
877
- fn run_send_unknown_task_renders_error_not_silent_swallow() {
870
+ fn run_send_legacy_task_flag_is_internalized_before_persistence() {
878
871
  let ws = std::env::temp_dir().join(format!(
879
872
  "ta-run-sendunk-{}-{}",
880
873
  std::process::id(),
@@ -906,30 +899,21 @@ fn run_send_unknown_task_renders_error_not_silent_swallow() {
906
899
  let code = run(&argv, &ws);
907
900
  assert_eq!(
908
901
  code,
909
- ExitCode::Error,
910
- "run(send --task <unknown>) must exit Error, not Ok"
911
- );
912
- // run() must have RENDERED (emit_cli_error wrote the cli-error log); a swallow leaves none.
913
- let logs_dir = ws.join(".team").join("logs");
914
- let mut found = String::new();
915
- if let Ok(entries) = std::fs::read_dir(&logs_dir) {
916
- for entry in entries.flatten() {
917
- let name = entry.file_name().to_string_lossy().to_string();
918
- if name.starts_with("cli-error-") {
919
- found = std::fs::read_to_string(entry.path()).unwrap_or_default();
920
- break;
921
- }
922
- }
923
- }
924
- assert!(
925
- found.contains("unknown task id: t-unknown"),
926
- "run() must RENDER the send error (cli-error log written with the bare message) — a silent \
927
- swallow (unwrap_or discards Err) leaves no log. got log body: {found:?}"
928
- );
929
- assert!(
930
- !found.contains("validation:"),
931
- "rendered error must be the bare golden message, NO 'validation:' prefix; got {found:?}"
902
+ ExitCode::Ok,
903
+ "legacy --task is sunset-noticed and ignored; public send persists without caller binding"
932
904
  );
905
+ let connection = rusqlite::Connection::open(
906
+ ws.join(".team").join("runtime").join("team.db"),
907
+ )
908
+ .unwrap();
909
+ let task_id: Option<String> = connection
910
+ .query_row(
911
+ "SELECT task_id FROM messages WHERE content = 'go' ORDER BY rowid DESC LIMIT 1",
912
+ [],
913
+ |row| row.get(0),
914
+ )
915
+ .unwrap();
916
+ assert_eq!(task_id, None, "caller-supplied task ids must not reach storage");
933
917
  let _ = std::fs::remove_dir_all(&ws);
934
918
  }
935
919
 
@@ -312,7 +312,7 @@ pub struct SendArgs {
312
312
  pub workspace: PathBuf,
313
313
  pub team: Option<String>,
314
314
  pub task: Option<String>,
315
- pub sender: String,
315
+ pub sender: TrustedSender,
316
316
  pub no_ack: bool,
317
317
  pub no_wait: bool,
318
318
  pub watch_result: bool,
@@ -323,16 +323,12 @@ pub struct SendArgs {
323
323
  /// When set, the store insert uses this id verbatim; a repeat with the same
324
324
  /// id returns a `Duplicate` refusal instead of creating a second row.
325
325
  pub message_id: Option<String>,
326
- /// F1 (0.3.26): `--pane <pane_id>` direct pane-id targeting. Mutually
327
- /// exclusive with `target` / `targets`. When set, the message is injected
328
- /// directly into the specified tmux pane via `transport.inject`, bypassing
329
- /// the agent-name → pane-id resolution + team-membership check. This is
330
- /// the **cross-team communication** primitive: the target pane does not
331
- /// need to be in the sender's team.
326
+ /// Deprecated compatibility input. Public send refuses pane identity and
327
+ /// requires a logical recipient so persistence always precedes delivery.
332
328
  pub pane: Option<String>,
333
329
  /// `--to-name <name>` — stable named addressing. Mutually exclusive with
334
330
  /// `target` / `targets` / `--pane`; resolves workspace/team/agent or leader
335
- /// name to the current live pane before using direct pane injection.
331
+ /// name before delegating to the persisted message primitive.
336
332
  pub to_name: Option<String>,
337
333
  /// E7 (0.5.9 host-leader-registry-design): `--to-leader NAME` resolves
338
334
  /// NAME through `~/.team-agent/leaders`, canonical-validates, and then
@@ -259,7 +259,7 @@ fn run_phase_golden(spec: PhaseGolden) -> Value {
259
259
  workspace: workspace.clone(),
260
260
  team: Some(spec.team_key.to_string()),
261
261
  task: None,
262
- sender: "leader".to_string(),
262
+ sender: crate::messaging::TrustedSender::leader(),
263
263
  no_ack: true,
264
264
  no_wait: true,
265
265
  watch_result: false,
@@ -88,7 +88,7 @@ use crate::message_store::MessageStore;
88
88
  use crate::state::persist::{load_runtime_state, save_runtime_state};
89
89
 
90
90
  // ── REUSE: step 11 messaging delegate surface ───────────────────────────────
91
- use crate::messaging::{self, DeliveryOutcome, MessageTarget, SendOptions};
91
+ use crate::messaging::{self, DeliveryOutcome, MessageTarget, SendOptions, TrustedSender};
92
92
 
93
93
  pub mod helpers;
94
94
  pub(crate) mod lifecycle_tools;
@@ -155,7 +155,6 @@ fn send_message_worker_recipient_returns_accepted_with_poll_hint() {
155
155
  None,
156
156
  None,
157
157
  None,
158
- None,
159
158
  );
160
159
  match outcome {
161
160
  Ok(SendOutcome::WorkerAccepted {
@@ -185,7 +184,6 @@ fn ordinary_send_assign_shape_has_no_recovery_marker() {
185
184
  None,
186
185
  None,
187
186
  None,
188
- None,
189
187
  )
190
188
  .expect("ordinary send ok")
191
189
  .to_value();
@@ -240,6 +238,22 @@ fn ordinary_send_assign_shape_has_no_recovery_marker() {
240
238
  );
241
239
  }
242
240
 
241
+ #[test]
242
+ fn send_message_without_framework_identity_fails_closed() {
243
+ let ws = seed_current_worker_state("missing-sender-identity");
244
+ let tools = TeamOrchestratorTools::with_identity(&ws, None, Some(TeamKey::new("current")));
245
+ let error = tools
246
+ .send_message(
247
+ &MessageTarget::Single("worker-1".to_string()),
248
+ "must not be attributed to unknown",
249
+ None,
250
+ None,
251
+ None,
252
+ )
253
+ .expect_err("missing framework identity must fail before persistence");
254
+ assert_eq!(error.reason, ToolErrorReason::McpScopeRefused);
255
+ }
256
+
243
257
  #[test]
244
258
  fn recovery_assign_shape_has_structured_marker() {
245
259
  let ws = seed_current_worker_state("recovery-marker");
@@ -304,7 +318,6 @@ fn send_message_worker_recipient_surfaces_dead_coordinator_warning() {
304
318
  None,
305
319
  None,
306
320
  None,
307
- None,
308
321
  )
309
322
  .expect("send returns degraded warning, not an MCP error");
310
323
  let v = outcome.to_value();
@@ -336,7 +349,6 @@ fn send_message_leader_recipient_is_direct_not_accepted() {
336
349
  None,
337
350
  None,
338
351
  None,
339
- None,
340
352
  )
341
353
  .expect("leader send ok");
342
354
  assert!(
@@ -435,7 +447,6 @@ fn send_message_cross_team_peer_surfaces_peer_not_in_scope_error() {
435
447
  None,
436
448
  None,
437
449
  None,
438
- None,
439
450
  )
440
451
  .expect_err("out-of-scope peer must be refused");
441
452
  assert_eq!(err.reason, ToolErrorReason::PeerNotInScope);
@@ -66,6 +66,12 @@
66
66
  );
67
67
  assert_eq!(send["inputSchema"]["additionalProperties"], json!(false));
68
68
  assert_eq!(send["inputSchema"]["required"], json!(["to", "content"]));
69
+ for internal in ["sender", "task_id", "requires_ack"] {
70
+ assert!(
71
+ send["inputSchema"]["properties"].get(internal).is_none(),
72
+ "{internal} is framework-owned, not caller-supplied"
73
+ );
74
+ }
69
75
  }
70
76
 
71
77
  #[test]
@@ -17,7 +17,7 @@ use crate::state::persist::{
17
17
  };
18
18
 
19
19
  // ── REUSE: step 11 messaging delegate surface ───────────────────────────────
20
- use crate::messaging::{self, MessageTarget, SendOptions};
20
+ use crate::messaging::{self, MessageTarget, SendOptions, TrustedSender};
21
21
 
22
22
  use super::helpers::{
23
23
  current_reportable_message_for, delivery_outcome_value, direct_message_attribution_for,
@@ -140,7 +140,6 @@ impl TeamOrchestratorTools {
140
140
  Some(task_id),
141
141
  None,
142
142
  None,
143
- None,
144
143
  )?;
145
144
  let mut ok = compact_tool_result(&out.to_value())?;
146
145
  if recovery {
@@ -155,7 +154,7 @@ impl TeamOrchestratorTools {
155
154
  }
156
155
 
157
156
  /// `send_message` (`tools.py:135-183`): C14/C15/C17 scope resolution.
158
- /// - sender = explicit / `TEAM_AGENT_ID` env / `"unknown"` (no candidate scan).
157
+ /// - sender = immutable `TEAM_AGENT_ID` captured when the MCP server starts.
159
158
  /// - `requires_ack` defaults from target (`_requires_ack_for_target`).
160
159
  /// - C23 cross-team pre-refusal ([`Self::refuse_cross_team_peer`]) before any
161
160
  /// runtime call.
@@ -168,7 +167,6 @@ impl TeamOrchestratorTools {
168
167
  to: &MessageTarget,
169
168
  content: &str,
170
169
  task_id: Option<&str>,
171
- sender: Option<&str>,
172
170
  requires_ack: Option<bool>,
173
171
  scope_override: Option<Scope>,
174
172
  ) -> Result<SendOutcome, ToolError> {
@@ -183,10 +181,14 @@ impl TeamOrchestratorTools {
183
181
  if let Some(err) = self.refuse_cross_team_peer(to, None) {
184
182
  return Err(err);
185
183
  }
186
- let sender = sender
187
- .and_then(non_empty_string)
188
- .or_else(|| self.agent_id.as_ref().map(AgentId::as_str))
189
- .unwrap_or("unknown");
184
+ let sender = self.agent_id.clone().ok_or_else(|| {
185
+ ToolError::new(
186
+ ToolErrorReason::McpScopeRefused,
187
+ "send_message requires framework-injected TEAM_AGENT_ID",
188
+ "IdentityError",
189
+ )
190
+ })?;
191
+ let sender = TrustedSender::from_runtime_identity(sender);
190
192
  let ack = requires_ack.unwrap_or_else(|| requires_ack_for_target(to));
191
193
  // C14/C15/C17 scope audit (#230 I-2/I-6 contract): emit mcp.scope_resolved
192
194
  // for every worker-origin send before any routing/delivery — the funnel
@@ -197,7 +199,7 @@ impl TeamOrchestratorTools {
197
199
  "mcp.scope_resolved",
198
200
  serde_json::json!({
199
201
  "tool": "send_message",
200
- "sender": sender,
202
+ "sender": sender.as_str(),
201
203
  "owner_team_id": canonical_owner_team.as_ref().map(TeamKey::as_str),
202
204
  "to": match to {
203
205
  MessageTarget::Single(t) => serde_json::Value::String(t.clone()),
@@ -213,7 +215,7 @@ impl TeamOrchestratorTools {
213
215
  let opts = SendOptions {
214
216
  task_id: task_id.map(TaskId::new),
215
217
  route_task_id: true,
216
- sender: sender.to_string(),
218
+ sender,
217
219
  requires_ack: ack,
218
220
  team: canonical_owner_team,
219
221
  ..SendOptions::default()
@@ -417,21 +417,6 @@ fn tool_properties(tool: McpTool) -> serde_json::Map<String, Value> {
417
417
  string_property("Target agent id, 'leader', or '*' for broadcast."),
418
418
  );
419
419
  insert_property(&mut properties, "content", string_property("Message body."));
420
- insert_property(
421
- &mut properties,
422
- "task_id",
423
- string_property("Optional task id to associate with the message."),
424
- );
425
- insert_property(
426
- &mut properties,
427
- "sender",
428
- string_property("Optional sender override."),
429
- );
430
- insert_property(
431
- &mut properties,
432
- "requires_ack",
433
- boolean_property("Whether the recipient should acknowledge delivery."),
434
- );
435
420
  }
436
421
  McpTool::ReportResult => {
437
422
  insert_property(
@@ -609,9 +594,8 @@ pub(crate) fn dispatch_tool(
609
594
  let outcome = tools.send_message(
610
595
  &target,
611
596
  content,
612
- args.get("task_id").and_then(Value::as_str),
613
- args.get("sender").and_then(Value::as_str),
614
- args.get("requires_ack").and_then(Value::as_bool),
597
+ None,
598
+ None,
615
599
  None,
616
600
  )?;
617
601
  match outcome {
@@ -96,6 +96,7 @@ pub use scheduler::{detect_stuck_agents, fire_due_scheduled_events, stuck_cancel
96
96
  pub use selftest::{evaluate_idle_behavior, run_comms_selftest, CommsSelftestDriver};
97
97
  pub use send::{
98
98
  apply_worker_sender_bypass, send_message, session_drift_refusal, MessageTarget, SendOptions,
99
+ TrustedSender,
99
100
  };
100
101
  pub use trust::{attempt_trust_auto_answer, TrustAnswerOutcome};
101
102
  pub use types::{
@@ -5,7 +5,7 @@ use std::path::Path;
5
5
  use crate::coordinator::{CoordinatorHealthStatus, WorkspacePath};
6
6
  use crate::event_log::EventLog;
7
7
  use crate::model::enums::PaneLiveness;
8
- use crate::model::ids::{TaskId, TeamKey};
8
+ use crate::model::ids::{AgentId, TaskId, TeamKey};
9
9
  use crate::transport::{PaneId, Transport};
10
10
 
11
11
  use super::helpers::{status_wire, MessageStatusShadow};
@@ -23,6 +23,29 @@ pub enum MessageTarget {
23
23
  Fanout(Vec<String>),
24
24
  }
25
25
 
26
+ /// Sender identity captured from a framework-owned runtime context.
27
+ ///
28
+ /// Public CLI/MCP inputs never construct this value from a caller-supplied
29
+ /// string. The wrapper keeps that trust boundary visible throughout delivery
30
+ /// instead of degrading the identity back to an untyped option field.
31
+ #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
32
+ #[serde(transparent)]
33
+ pub struct TrustedSender(AgentId);
34
+
35
+ impl TrustedSender {
36
+ pub fn from_runtime_identity(agent_id: AgentId) -> Self {
37
+ Self(agent_id)
38
+ }
39
+
40
+ pub fn leader() -> Self {
41
+ Self(AgentId::new("leader"))
42
+ }
43
+
44
+ pub fn as_str(&self) -> &str {
45
+ self.0.as_str()
46
+ }
47
+ }
48
+
26
49
  /// `send_message` 选项 (`send.py:36`:Python 大量默认参数 → typed 选项 struct)。
27
50
  #[derive(Debug, Clone)]
28
51
  pub struct SendOptions {
@@ -31,7 +54,7 @@ pub struct SendOptions {
31
54
  /// `task_id` 当真任务校验/路由。**投递/fanout/internal/coordinator** 路径传 `false`
32
55
  /// (`internal_delivery.py:44`、`send.py:412/481`),此时 `task_id` 只是标签,**不校验 state.tasks**。
33
56
  pub route_task_id: bool,
34
- pub sender: String,
57
+ pub sender: TrustedSender,
35
58
  pub requires_ack: bool,
36
59
  pub confirm_human: bool,
37
60
  pub wait_visible: bool,
@@ -52,7 +75,7 @@ impl Default for SendOptions {
52
75
  Self {
53
76
  task_id: None,
54
77
  route_task_id: true,
55
- sender: "leader".to_string(),
78
+ sender: TrustedSender::leader(),
56
79
  requires_ack: true,
57
80
  confirm_human: false,
58
81
  wait_visible: true,
@@ -95,7 +118,7 @@ pub fn send_message(
95
118
  "leader",
96
119
  content,
97
120
  opts.task_id.as_ref(),
98
- &opts.sender,
121
+ opts.sender.as_str(),
99
122
  opts.requires_ack,
100
123
  None,
101
124
  opts.message_id.as_deref(),
@@ -119,7 +142,7 @@ pub fn send_message(
119
142
  }
120
143
  MessageTarget::Single(target) => target,
121
144
  MessageTarget::Broadcast => {
122
- let recipients = broadcast_recipients(&state, &opts.sender, opts.team.as_ref());
145
+ let recipients = broadcast_recipients(&state, opts.sender.as_str(), opts.team.as_ref());
123
146
  return fanout_send(
124
147
  workspace,
125
148
  &state,
@@ -163,13 +186,13 @@ pub fn send_message(
163
186
  &state,
164
187
  recipient,
165
188
  "leader",
166
- &opts.sender,
189
+ opts.sender.as_str(),
167
190
  opts.task_id.as_ref(),
168
191
  &event_log,
169
192
  )? {
170
193
  return Ok(outcome);
171
194
  }
172
- if let Some(outcome) = send_owner_gate_refusal(workspace, &state, &opts.sender)? {
195
+ if let Some(outcome) = send_owner_gate_refusal(workspace, &state, opts.sender.as_str())? {
173
196
  return Ok(outcome);
174
197
  }
175
198
  if opts.route_task_id {
@@ -206,7 +229,7 @@ pub fn send_message(
206
229
  store.create_message_with_id(
207
230
  requested,
208
231
  task_id,
209
- &opts.sender,
232
+ opts.sender.as_str(),
210
233
  recipient,
211
234
  content,
212
235
  None,
@@ -216,7 +239,7 @@ pub fn send_message(
216
239
  } else {
217
240
  store.create_message(
218
241
  task_id,
219
- &opts.sender,
242
+ opts.sender.as_str(),
220
243
  recipient,
221
244
  content,
222
245
  None,
@@ -690,7 +713,7 @@ fn fanout_send(
690
713
  let mut delivered_count = 0usize;
691
714
  let mut attempted_count = 0usize;
692
715
  for recipient in recipients {
693
- if recipient.is_empty() || recipient == &opts.sender {
716
+ if recipient.is_empty() || recipient == opts.sender.as_str() {
694
717
  continue;
695
718
  }
696
719
  attempted_count = attempted_count.saturating_add(1);
@@ -701,7 +724,7 @@ fn fanout_send(
701
724
  recipient,
702
725
  content,
703
726
  opts.task_id.as_ref(),
704
- &opts.sender,
727
+ opts.sender.as_str(),
705
728
  opts.requires_ack,
706
729
  None,
707
730
  event_log,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.5.49",
3
+ "version": "0.5.50",
4
4
  "description": "npx installer for Team Agent",
5
5
  "keywords": [
6
6
  "codex",
@@ -20,9 +20,9 @@
20
20
  "team-agent-installer": "npm/install.mjs"
21
21
  },
22
22
  "optionalDependencies": {
23
- "@team-agent/cli-darwin-arm64": "0.5.49",
24
- "@team-agent/cli-darwin-x64": "0.5.49",
25
- "@team-agent/cli-linux-x64": "0.5.49"
23
+ "@team-agent/cli-darwin-arm64": "0.5.50",
24
+ "@team-agent/cli-darwin-x64": "0.5.50",
25
+ "@team-agent/cli-linux-x64": "0.5.50"
26
26
  },
27
27
  "scripts": {
28
28
  "postinstall": "node npm/bincheck.mjs",