@team-agent/installer 0.5.52 → 0.5.54

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 (87) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +18 -2
  4. package/crates/team-agent/src/cli/emit.rs +111 -3
  5. package/crates/team-agent/src/cli/mod.rs +155 -4
  6. package/crates/team-agent/src/cli/send/persist.rs +1 -0
  7. package/crates/team-agent/src/cli/send/presentation.rs +1 -0
  8. package/crates/team-agent/src/cli/send.rs +1 -0
  9. package/crates/team-agent/src/cli/spec.rs +4 -1
  10. package/crates/team-agent/src/cli/tests/lane_c.rs +3 -3
  11. package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -0
  12. package/crates/team-agent/src/cli/tests/named_address.rs +1 -0
  13. package/crates/team-agent/src/cli/tests/status_send.rs +1 -0
  14. package/crates/team-agent/src/cli/types.rs +12 -0
  15. package/crates/team-agent/src/coordinator/tests/basics.rs +4 -4
  16. package/crates/team-agent/src/db/message_store.rs +31 -2
  17. package/crates/team-agent/src/db/migration.rs +7 -6
  18. package/crates/team-agent/src/db/schema.rs +18 -5
  19. package/crates/team-agent/src/diagnose/orphans.rs +24 -3
  20. package/crates/team-agent/src/kill_audit.rs +67 -0
  21. package/crates/team-agent/src/leader/lease.rs +324 -57
  22. package/crates/team-agent/src/leader/rediscover/tests.rs +3 -0
  23. package/crates/team-agent/src/leader/rediscover.rs +6 -0
  24. package/crates/team-agent/src/leader/start.rs +144 -23
  25. package/crates/team-agent/src/leader/tests/idle.rs +3 -0
  26. package/crates/team-agent/src/leader/types.rs +6 -0
  27. package/crates/team-agent/src/lib.rs +1 -0
  28. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +1 -1
  29. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +5 -0
  30. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +4 -0
  31. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +106 -0
  32. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +216 -208
  33. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +36 -0
  34. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +238 -0
  35. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +101 -5
  36. package/crates/team-agent/src/lifecycle/launch/role_source.rs +170 -0
  37. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +1 -1
  38. package/crates/team-agent/src/lifecycle/launch.rs +14 -2
  39. package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -1
  40. package/crates/team-agent/src/lifecycle/restart/common.rs +12 -0
  41. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -3
  42. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +110 -12
  43. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +8 -2
  44. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +1 -0
  45. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +3 -1
  46. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +1 -0
  47. package/crates/team-agent/src/lifecycle/types.rs +17 -0
  48. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +40 -5
  49. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -1
  50. package/crates/team-agent/src/mcp_server/normalize.rs +8 -0
  51. package/crates/team-agent/src/mcp_server/tests/wire.rs +243 -167
  52. package/crates/team-agent/src/mcp_server/tools.rs +89 -4
  53. package/crates/team-agent/src/mcp_server/types.rs +7 -0
  54. package/crates/team-agent/src/mcp_server/wire.rs +65 -4
  55. package/crates/team-agent/src/messaging/delivery.rs +2 -0
  56. package/crates/team-agent/src/messaging/helpers.rs +1 -0
  57. package/crates/team-agent/src/messaging/leader_channel.rs +32 -5
  58. package/crates/team-agent/src/messaging/leader_receiver.rs +100 -21
  59. package/crates/team-agent/src/messaging/mod.rs +2 -1
  60. package/crates/team-agent/src/messaging/persist.rs +68 -2
  61. package/crates/team-agent/src/messaging/presentation.rs +307 -0
  62. package/crates/team-agent/src/messaging/results.rs +130 -3
  63. package/crates/team-agent/src/messaging/selftest.rs +1 -0
  64. package/crates/team-agent/src/messaging/send.rs +54 -2
  65. package/crates/team-agent/src/messaging/tests/runtime.rs +85 -24
  66. package/crates/team-agent/src/messaging/types.rs +2 -0
  67. package/crates/team-agent/src/messaging/watchers.rs +1 -0
  68. package/crates/team-agent/src/provider/adapter.rs +54 -13
  69. package/crates/team-agent/src/provider/adapters/claude_fork.rs +122 -0
  70. package/crates/team-agent/src/provider/adapters/copilot_fork.rs +306 -0
  71. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  72. package/crates/team-agent/src/provider/session/capture.rs +395 -52
  73. package/crates/team-agent/src/provider/session/context_fork.rs +499 -0
  74. package/crates/team-agent/src/provider/session/mod.rs +6 -0
  75. package/crates/team-agent/src/provider/session_scan/claude.rs +1 -1
  76. package/crates/team-agent/src/provider/session_scan/codex.rs +144 -27
  77. package/crates/team-agent/src/provider/session_scan/common/tests.rs +112 -0
  78. package/crates/team-agent/src/provider/session_scan/common.rs +53 -92
  79. package/crates/team-agent/src/provider/session_scan/copilot.rs +47 -3
  80. package/crates/team-agent/src/provider/session_scan.rs +3 -3
  81. package/crates/team-agent/src/provider/tests/copilot_fork.rs +191 -0
  82. package/crates/team-agent/src/provider/tests.rs +1 -0
  83. package/crates/team-agent/src/tmux_backend/tests.rs +51 -16
  84. package/crates/team-agent/src/tmux_backend.rs +29 -2
  85. package/package.json +4 -4
  86. package/schemas/result-envelope.schema.json +10 -0
  87. package/skills/team-agent/SKILL.md +11 -3
@@ -85,6 +85,7 @@ pub struct NotificationClaimParams<'a> {
85
85
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
86
86
  pub enum MessageRowStatus {
87
87
  Accepted,
88
+ StoredOnly,
88
89
  QueuedUntilLeaderAttach,
89
90
  QueuedCoordinatorUnavailable,
90
91
  }
@@ -93,6 +94,7 @@ impl MessageRowStatus {
93
94
  pub const fn as_str(self) -> &'static str {
94
95
  match self {
95
96
  Self::Accepted => "accepted",
97
+ Self::StoredOnly => "stored_only",
96
98
  Self::QueuedUntilLeaderAttach => "queued_until_leader_attach",
97
99
  Self::QueuedCoordinatorUnavailable => "queued_coordinator_unavailable",
98
100
  }
@@ -111,6 +113,7 @@ pub struct PersistMessageInput<'a> {
111
113
  pub requires_ack: bool,
112
114
  pub status: MessageRowStatus,
113
115
  pub content: &'a str,
116
+ pub presentation: &'a str,
114
117
  pub error: Option<&'a str>,
115
118
  }
116
119
 
@@ -182,9 +185,9 @@ impl MessageStore {
182
185
  conn.execute(
183
186
  "insert into messages(
184
187
  message_id, owner_team_id, task_id, sender, recipient, reply_to, requires_ack,
185
- status, content, artifact_refs, created_at, updated_at, delivered_at,
188
+ status, content, presentation, artifact_refs, created_at, updated_at, delivered_at,
186
189
  acknowledged_at, error, delivery_attempts
187
- ) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, '[]', ?10, ?10, null, null, ?11, 0)",
190
+ ) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, '[]', ?11, ?11, null, null, ?12, 0)",
188
191
  params![
189
192
  message_id,
190
193
  input.owner_team_id,
@@ -195,6 +198,7 @@ impl MessageStore {
195
198
  if input.requires_ack { 1 } else { 0 },
196
199
  input.status.as_str(),
197
200
  input.content,
201
+ input.presentation,
198
202
  now,
199
203
  input.error,
200
204
  ],
@@ -226,6 +230,7 @@ impl MessageStore {
226
230
  requires_ack,
227
231
  status: MessageRowStatus::Accepted,
228
232
  content,
233
+ presentation: r#"{"sink":"leader","class":"message"}"#,
229
234
  error: None,
230
235
  })
231
236
  }
@@ -259,6 +264,7 @@ impl MessageStore {
259
264
  requires_ack,
260
265
  status: MessageRowStatus::Accepted,
261
266
  content,
267
+ presentation: r#"{"sink":"leader","class":"message"}"#,
262
268
  error: None,
263
269
  })
264
270
  }
@@ -809,6 +815,29 @@ mod tests {
809
815
  assert_eq!(status_of(&read(&s), &mid), "failed");
810
816
  }
811
817
 
818
+ #[test]
819
+ fn claim_for_delivery_never_claims_stored_only_presentation() {
820
+ let s = store();
821
+ let mid = s
822
+ .persist_message(PersistMessageInput {
823
+ message_id: None,
824
+ owner_team_id: Some("team-a"),
825
+ task_id: None,
826
+ sender: "worker",
827
+ recipient: "leader",
828
+ reply_to: None,
829
+ requires_ack: false,
830
+ status: MessageRowStatus::StoredOnly,
831
+ content: "casefile evidence",
832
+ presentation: r#"{"sink":"casefile","class":"stage_result"}"#,
833
+ error: None,
834
+ })
835
+ .unwrap();
836
+ assert!(!s.claim_for_delivery(&mid).unwrap());
837
+ assert_eq!(status_of(&read(&s), &mid), "stored_only");
838
+ assert_eq!(col_i64(&read(&s), &mid, "delivery_attempts"), 0);
839
+ }
840
+
812
841
  // ───────────────────────────── mark state machine ─────────────────────────────
813
842
 
814
843
  #[test]
@@ -26,6 +26,7 @@ pub const MANAGED_TABLE_LAYOUTS: &[(&str, &[&str])] = &[
26
26
  "requires_ack",
27
27
  "status",
28
28
  "content",
29
+ "presentation",
29
30
  "artifact_refs",
30
31
  "created_at",
31
32
  "updated_at",
@@ -121,7 +122,7 @@ pub const MANAGED_TABLE_LAYOUTS: &[(&str, &[&str])] = &[
121
122
 
122
123
  /// rebuild / 建缺表用的 DDL 模板(`schema_migration.py:CREATE_TABLE_SQL`,`__TABLE__` 占位)。
123
124
  const CREATE_TABLE_TEMPLATES: &[(&str, &str)] = &[
124
- ("messages", "create table if not exists __TABLE__ (\n message_id text primary key,\n owner_team_id text,\n task_id text,\n sender text,\n recipient text,\n reply_to text,\n requires_ack integer,\n status text,\n content text,\n artifact_refs text,\n created_at text,\n updated_at text,\n delivered_at text,\n acknowledged_at text,\n error text,\n delivery_attempts integer not null default 0\n )"),
125
+ ("messages", "create table if not exists __TABLE__ (\n message_id text primary key,\n owner_team_id text,\n task_id text,\n sender text,\n recipient text,\n reply_to text,\n requires_ack integer,\n status text,\n content text,\n presentation text not null default '{\"sink\":\"leader\",\"class\":\"message\"}',\n artifact_refs text,\n created_at text,\n updated_at text,\n delivered_at text,\n acknowledged_at text,\n error text,\n delivery_attempts integer not null default 0\n )"),
125
126
  ("results", "create table if not exists __TABLE__ (\n result_id text primary key,\n owner_team_id text,\n task_id text not null,\n agent_id text not null,\n envelope text not null,\n status text not null,\n created_at text not null\n )"),
126
127
  ("scheduled_events", "create table if not exists __TABLE__ (\n id integer primary key,\n owner_team_id text,\n due_at text not null,\n target text not null,\n kind text not null,\n payload_json text not null,\n status text not null,\n created_at text not null,\n fired_at text,\n result_json text\n )"),
127
128
  ("delivery_tokens", "create table if not exists __TABLE__ (\n message_id text primary key,\n unique_token text not null,\n injected_at text not null,\n visible_at text,\n consumed_at text,\n failed_at text,\n failure_reason text\n )"),
@@ -641,11 +642,11 @@ mod tests {
641
642
  assert_eq!(m1, "t");
642
643
  drop(conn);
643
644
 
644
- // 迁移后 diagnosis = ok / user_version=3
645
+ // 迁移后 diagnosis = ok / current user_version。
645
646
  let after = schema_diagnosis(&path, SCHEMA_VERSION).unwrap();
646
647
  assert!(after.ok);
647
648
  assert_eq!(after.status, "ok");
648
- assert_eq!(after.user_version, 3);
649
+ assert_eq!(after.user_version, SCHEMA_VERSION);
649
650
 
650
651
  // 备份文件已写(team.db.pre-migration-*-from-v1.bak)。
651
652
  let runtime = path.parent().unwrap();
@@ -670,7 +671,7 @@ mod tests {
670
671
  let d = schema_diagnosis(&path, SCHEMA_VERSION).unwrap();
671
672
  assert!(d.ok);
672
673
  assert_eq!(d.status, "ok");
673
- assert_eq!(d.user_version, 3);
674
+ assert_eq!(d.user_version, SCHEMA_VERSION);
674
675
  }
675
676
 
676
677
  #[test]
@@ -788,7 +789,7 @@ mod tests {
788
789
  initialize_schema(&conn, Some(&path)).unwrap();
789
790
  let cols = table_layout(&conn, "messages").unwrap();
790
791
  assert!(!cols.iter().any(|c| c == "legacy_junk"), "废列应被丢弃");
791
- assert_eq!(cols.len(), 16);
792
+ assert_eq!(cols.len(), 17);
792
793
  assert_eq!(table_count(&conn, "messages").unwrap(), 1);
793
794
  }
794
795
 
@@ -846,7 +847,7 @@ mod tests {
846
847
  rebuilds,
847
848
  } => {
848
849
  assert!(diagnosis.ok);
849
- assert_eq!(diagnosis.user_version, 3);
850
+ assert_eq!(diagnosis.user_version, SCHEMA_VERSION);
850
851
  assert_eq!(rebuilds.len(), 8);
851
852
  }
852
853
  other => panic!("expected Fixed, got {other:?}"),
@@ -12,11 +12,11 @@ use rusqlite::Connection;
12
12
  use crate::db::DbError;
13
13
 
14
14
  /// `schema.py:90`。
15
- pub const SCHEMA_VERSION: i64 = 3;
15
+ pub const SCHEMA_VERSION: i64 = 4;
16
16
 
17
17
  /// 8 张表的 DDL(逐字照搬 `schema.py:initialize_schema` 的内联建表;含 `if not exists`)。
18
18
  /// 顺序与 Python 一致(leader_notification_log 在 ensure 块后创建)。
19
- const CREATE_MESSAGES: &str = "create table if not exists messages (\n message_id text primary key,\n owner_team_id text,\n task_id text,\n sender text,\n recipient text,\n reply_to text,\n requires_ack integer,\n status text,\n content text,\n artifact_refs text,\n created_at text,\n updated_at text,\n delivered_at text,\n acknowledged_at text,\n error text,\n delivery_attempts integer not null default 0\n )";
19
+ const CREATE_MESSAGES: &str = "create table if not exists messages (\n message_id text primary key,\n owner_team_id text,\n task_id text,\n sender text,\n recipient text,\n reply_to text,\n requires_ack integer,\n status text,\n content text,\n presentation text not null default '{\"sink\":\"leader\",\"class\":\"message\"}',\n artifact_refs text,\n created_at text,\n updated_at text,\n delivered_at text,\n acknowledged_at text,\n error text,\n delivery_attempts integer not null default 0\n )";
20
20
  const CREATE_RESULTS: &str = "create table if not exists results (\n result_id text primary key,\n owner_team_id text,\n task_id text not null,\n agent_id text not null,\n envelope text not null,\n status text not null,\n created_at text not null\n )";
21
21
  const CREATE_SCHEDULED_EVENTS: &str = "create table if not exists scheduled_events (\n id integer primary key,\n owner_team_id text,\n due_at text not null,\n target text not null,\n kind text not null,\n payload_json text not null,\n status text not null,\n created_at text not null,\n fired_at text,\n result_json text\n )";
22
22
  const CREATE_DELIVERY_TOKENS: &str = "create table if not exists delivery_tokens (\n message_id text primary key,\n unique_token text not null,\n injected_at text not null,\n visible_at text,\n consumed_at text,\n failed_at text,\n failure_reason text\n )";
@@ -47,6 +47,7 @@ const MESSAGE_COLUMNS: &[&str] = &[
47
47
  "requires_ack",
48
48
  "status",
49
49
  "content",
50
+ "presentation",
50
51
  "artifact_refs",
51
52
  "created_at",
52
53
  "updated_at",
@@ -264,6 +265,10 @@ pub fn initialize_schema(
264
265
  "owner_team_id",
265
266
  "alter table messages add column owner_team_id text",
266
267
  ),
268
+ (
269
+ "presentation",
270
+ "alter table messages add column presentation text not null default '{\"sink\":\"leader\",\"class\":\"message\"}'",
271
+ ),
267
272
  ],
268
273
  )?;
269
274
  ensure_table_columns(
@@ -354,13 +359,13 @@ mod tests {
354
359
  }
355
360
 
356
361
  #[test]
357
- fn user_version_is_three() {
362
+ fn user_version_is_four() {
358
363
  let conn = fresh();
359
364
  let v: i64 = conn
360
365
  .query_row("pragma user_version", [], |r| r.get(0))
361
366
  .unwrap();
362
367
  assert_eq!(v, SCHEMA_VERSION);
363
- assert_eq!(v, 3);
368
+ assert_eq!(v, 4);
364
369
  }
365
370
 
366
371
  #[test]
@@ -378,6 +383,7 @@ mod tests {
378
383
  "requires_ack",
379
384
  "status",
380
385
  "content",
386
+ "presentation",
381
387
  "artifact_refs",
382
388
  "created_at",
383
389
  "updated_at",
@@ -394,7 +400,7 @@ mod tests {
394
400
  // initialize_schema 二次调用(if not exists / 无缺列)→ 不报错、schema 不变 + 索引仍在。
395
401
  let conn = fresh();
396
402
  initialize_schema(&conn, None).unwrap();
397
- assert_eq!(table_layout(&conn, "messages").unwrap().len(), 16);
403
+ assert_eq!(table_layout(&conn, "messages").unwrap().len(), 17);
398
404
  let idx: i64 = conn
399
405
  .query_row(
400
406
  "select count(*) from sqlite_master where type='index' and sql is not null",
@@ -422,6 +428,13 @@ mod tests {
422
428
  ("requires_ack", "INTEGER", 0, None, 0),
423
429
  ("status", "TEXT", 0, None, 0),
424
430
  ("content", "TEXT", 0, None, 0),
431
+ (
432
+ "presentation",
433
+ "TEXT",
434
+ 1,
435
+ Some("'{\"sink\":\"leader\",\"class\":\"message\"}'"),
436
+ 0,
437
+ ),
425
438
  ("artifact_refs", "TEXT", 0, None, 0),
426
439
  ("created_at", "TEXT", 0, None, 0),
427
440
  ("updated_at", "TEXT", 0, None, 0),
@@ -48,6 +48,14 @@ impl OrphanScanScope {
48
48
  canonical_current_workspace: canonicalize_workspace(workspace),
49
49
  }
50
50
  }
51
+
52
+ fn workspace(&self) -> &Path {
53
+ match self {
54
+ Self::CurrentWorkspace {
55
+ canonical_current_workspace,
56
+ } => canonical_current_workspace,
57
+ }
58
+ }
51
59
  }
52
60
 
53
61
  fn canonicalize_workspace(workspace: &Path) -> PathBuf {
@@ -262,7 +270,7 @@ fn cleanup_report(report: ScanReport, scope: &OrphanScanScope) -> CleanupReport
262
270
  }
263
271
  continue;
264
272
  }
265
- if kill_tmux_session(orphan) {
273
+ if kill_tmux_session(orphan, scope.workspace()) {
266
274
  killed.push(orphan_value(orphan, "killed"));
267
275
  } else {
268
276
  failed.push(orphan_value(orphan, "failed"));
@@ -790,14 +798,27 @@ fn orphan_value(orphan: &OrphanRecord, action: &str) -> Value {
790
798
  value
791
799
  }
792
800
 
793
- fn kill_tmux_session(orphan: &OrphanRecord) -> bool {
801
+ fn kill_tmux_session(orphan: &OrphanRecord, audit_workspace: &Path) -> bool {
794
802
  let (Some(socket), Some(session)) = (&orphan.tmux_socket, &orphan.session) else {
795
803
  return false;
796
804
  };
797
805
  // Phase 1d Batch 6: factory tmux-socket-name helper. Explicit
798
806
  // tmux-only kill; conpty shim orphans are NOT routed here (design
799
807
  // §Batch 6).
800
- crate::transport_factory::tmux_socket_name_transport(socket)
808
+ let transport = crate::transport_factory::tmux_socket_name_transport(socket);
809
+ let targets = vec![session.clone()];
810
+ if crate::kill_audit::pre_kill_audit(
811
+ &crate::event_log::EventLog::new(audit_workspace),
812
+ &transport,
813
+ "diagnose.orphans.tmux_session",
814
+ crate::kill_audit::KILL_SESSION,
815
+ &targets,
816
+ )
817
+ .is_err()
818
+ {
819
+ return false;
820
+ }
821
+ transport
801
822
  .kill_session(&SessionName::new(session.clone()))
802
823
  .is_ok()
803
824
  }
@@ -0,0 +1,67 @@
1
+ //! Durable, fail-closed observation immediately before destructive transport calls.
2
+
3
+ use std::path::Path;
4
+
5
+ use serde_json::json;
6
+
7
+ use crate::event_log::{EventLog, EventLogError};
8
+ use crate::transport::{BackendKind, Transport};
9
+
10
+ pub(crate) const KILL_SERVER: &str = "kill-server";
11
+ pub(crate) const KILL_SESSION: &str = "kill-session";
12
+
13
+ pub(crate) fn pre_kill_audit(
14
+ event_log: &EventLog,
15
+ transport: &dyn Transport,
16
+ caller: &str,
17
+ action: &str,
18
+ targets: &[String],
19
+ ) -> Result<(), EventLogError> {
20
+ let endpoint = transport.tmux_endpoint();
21
+ let argv = destructive_argv(transport.kind(), endpoint.as_deref(), action, targets);
22
+ event_log.write(
23
+ "transport.pre_kill_audit",
24
+ json!({
25
+ "phase": "pre_call",
26
+ "caller": caller,
27
+ "caller_pid": std::process::id(),
28
+ "action": action,
29
+ "argv": argv,
30
+ "endpoint": endpoint,
31
+ "targets": targets,
32
+ }),
33
+ )?;
34
+ Ok(())
35
+ }
36
+
37
+ fn destructive_argv(
38
+ kind: BackendKind,
39
+ endpoint: Option<&str>,
40
+ action: &str,
41
+ targets: &[String],
42
+ ) -> Vec<String> {
43
+ let mut argv = if kind == BackendKind::Tmux {
44
+ let mut argv = vec!["tmux".to_string()];
45
+ if let Some(endpoint) = endpoint.filter(|endpoint| !endpoint.is_empty()) {
46
+ if endpoint != "default" {
47
+ argv.push(if Path::new(endpoint).is_absolute() {
48
+ "-S".to_string()
49
+ } else {
50
+ "-L".to_string()
51
+ });
52
+ argv.push(endpoint.to_string());
53
+ }
54
+ }
55
+ argv
56
+ } else {
57
+ vec![format!("{kind:?}").to_ascii_lowercase()]
58
+ };
59
+ argv.push(action.to_string());
60
+ if action == KILL_SESSION {
61
+ for target in targets {
62
+ argv.push("-t".to_string());
63
+ argv.push(target.clone());
64
+ }
65
+ }
66
+ argv
67
+ }