@team-agent/installer 0.5.50 → 0.5.52

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 (93) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +5 -3
  4. package/crates/team-agent/src/cli/emit.rs +2 -1
  5. package/crates/team-agent/src/cli/mod.rs +32 -14
  6. package/crates/team-agent/src/cli/named_address.rs +46 -115
  7. package/crates/team-agent/src/cli/send/coordinator.rs +163 -0
  8. package/crates/team-agent/src/cli/send/mailbox.rs +99 -0
  9. package/crates/team-agent/src/cli/send/persist.rs +154 -0
  10. package/crates/team-agent/src/cli/send/presentation.rs +333 -0
  11. package/crates/team-agent/src/cli/send/resolve.rs +361 -0
  12. package/crates/team-agent/src/cli/send.rs +44 -1169
  13. package/crates/team-agent/src/cli/spec.rs +1 -1
  14. package/crates/team-agent/src/cli/status_port/agents.rs +358 -0
  15. package/crates/team-agent/src/cli/status_port/approvals.rs +79 -0
  16. package/crates/team-agent/src/cli/status_port/compact.rs +207 -0
  17. package/crates/team-agent/src/cli/status_port/format.rs +145 -0
  18. package/crates/team-agent/src/cli/status_port/inbox.rs +36 -0
  19. package/crates/team-agent/src/cli/status_port/runtime.rs +195 -0
  20. package/crates/team-agent/src/cli/status_port/snapshot.rs +181 -0
  21. package/crates/team-agent/src/cli/status_port/store.rs +412 -0
  22. package/crates/team-agent/src/cli/status_port/tests.rs +54 -0
  23. package/crates/team-agent/src/cli/status_port.rs +47 -1548
  24. package/crates/team-agent/src/cli/tests/main_preserved.rs +1 -0
  25. package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -0
  26. package/crates/team-agent/src/cli/types.rs +1 -0
  27. package/crates/team-agent/src/coordinator/conpty_shim.rs +34 -30
  28. package/crates/team-agent/src/coordinator/steps/abnormal.rs +135 -13
  29. package/crates/team-agent/src/coordinator/tick.rs +37 -0
  30. package/crates/team-agent/src/db/agent_health_capture.rs +18 -13
  31. package/crates/team-agent/src/db/message_store.rs +154 -44
  32. package/crates/team-agent/src/event_log.rs +73 -0
  33. package/crates/team-agent/src/leader/lease.rs +202 -16
  34. package/crates/team-agent/src/leader/start.rs +28 -4
  35. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +424 -0
  36. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +303 -0
  37. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +160 -0
  38. package/crates/team-agent/src/lifecycle/launch/approval.rs +134 -0
  39. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +492 -0
  40. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +297 -0
  41. package/crates/team-agent/src/lifecycle/launch/identity.rs +372 -0
  42. package/crates/team-agent/src/lifecycle/launch/layout.rs +313 -0
  43. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +478 -0
  44. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +201 -0
  45. package/crates/team-agent/src/lifecycle/launch/ownership.rs +66 -0
  46. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +477 -0
  47. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +278 -0
  48. package/crates/team-agent/src/lifecycle/launch/readiness.rs +123 -0
  49. package/crates/team-agent/src/lifecycle/launch/spawn.rs +377 -0
  50. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +434 -0
  51. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +499 -0
  52. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +438 -0
  53. package/crates/team-agent/src/lifecycle/launch.rs +119 -5351
  54. package/crates/team-agent/src/lifecycle/restart/agent.rs +63 -29
  55. package/crates/team-agent/src/lifecycle/restart/common.rs +53 -27
  56. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +167 -27
  57. package/crates/team-agent/src/lifecycle/restart/remove.rs +503 -77
  58. package/crates/team-agent/src/lifecycle/restart.rs +26 -2
  59. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +906 -17
  60. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +67 -2
  61. package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +24 -1
  62. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +30 -7
  63. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +8 -4
  64. package/crates/team-agent/src/mcp_server/mod.rs +1 -1
  65. package/crates/team-agent/src/mcp_server/tests/send.rs +6 -10
  66. package/crates/team-agent/src/mcp_server/tests/tools.rs +18 -1
  67. package/crates/team-agent/src/mcp_server/tests.rs +11 -4
  68. package/crates/team-agent/src/mcp_server/tools.rs +14 -5
  69. package/crates/team-agent/src/messaging/activity.rs +4 -2
  70. package/crates/team-agent/src/messaging/address.rs +86 -0
  71. package/crates/team-agent/src/messaging/delivery.rs +288 -183
  72. package/crates/team-agent/src/messaging/helpers.rs +17 -13
  73. package/crates/team-agent/src/messaging/leader_channel.rs +171 -0
  74. package/crates/team-agent/src/messaging/leader_receiver.rs +60 -35
  75. package/crates/team-agent/src/messaging/mod.rs +15 -2
  76. package/crates/team-agent/src/messaging/persist.rs +309 -0
  77. package/crates/team-agent/src/messaging/results.rs +16 -24
  78. package/crates/team-agent/src/messaging/scheduler.rs +4 -2
  79. package/crates/team-agent/src/messaging/selftest.rs +19 -12
  80. package/crates/team-agent/src/messaging/send.rs +101 -49
  81. package/crates/team-agent/src/messaging/tests/leader_channel.rs +169 -0
  82. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +305 -0
  83. package/crates/team-agent/src/messaging/tests/mod.rs +2 -0
  84. package/crates/team-agent/src/messaging/tests/runtime.rs +38 -17
  85. package/crates/team-agent/src/messaging/watchers.rs +13 -3
  86. package/crates/team-agent/src/redaction.rs +72 -2
  87. package/crates/team-agent/src/state/persist.rs +284 -21
  88. package/crates/team-agent/src/state/projection.rs +245 -30
  89. package/crates/team-agent/src/state/repository/intent.rs +13 -0
  90. package/crates/team-agent/src/state/repository/tests.rs +47 -0
  91. package/crates/team-agent/src/state/repository.rs +94 -27
  92. package/crates/team-agent/src/state/selector.rs +9 -18
  93. package/package.json +4 -4
@@ -0,0 +1,361 @@
1
+ use super::mailbox::maybe_enqueue_offline_leader_mailbox;
2
+ use super::persist::persist_resolved_target;
3
+ use crate::cli::{CliError, SendArgs};
4
+ use crate::messaging::{MessageTarget, TrustedSender};
5
+ use serde_json::{json, Value};
6
+
7
+ pub(super) fn warn_send_alias(flag: &str) {
8
+ let spec = crate::cli::spec::command_spec("send");
9
+ let sunset = spec
10
+ .and_then(|spec| spec.sunset)
11
+ .unwrap_or("next compatibility release");
12
+ let action = spec
13
+ .and_then(|spec| spec.action)
14
+ .unwrap_or("use positional logical TARGET addressing");
15
+ eprintln!("warning: {flag} is deprecated; sunset: {sunset}; action: {action}");
16
+ }
17
+
18
+ pub(super) fn logical_to_from_args(
19
+ args: &SendArgs,
20
+ host_leader_to: Option<&str>,
21
+ ) -> Result<String, CliError> {
22
+ if args.to_name.is_some()
23
+ && (args.target.is_some() || args.targets.is_some() || args.to_leader.is_some())
24
+ {
25
+ return Err(CliError::Usage(
26
+ "--to-name and --pane/TARGET/--to are mutually exclusive".to_string(),
27
+ ));
28
+ }
29
+ let supplied = [
30
+ args.target.is_some(),
31
+ args.targets.is_some(),
32
+ args.to_name.is_some(),
33
+ args.to_leader.is_some(),
34
+ ]
35
+ .into_iter()
36
+ .filter(|present| *present)
37
+ .count();
38
+ if supplied > 1 {
39
+ return Err(CliError::Usage(
40
+ "TARGET, --targets, --to-name, and --to-leader are mutually exclusive".to_string(),
41
+ ));
42
+ }
43
+ let logical_to = if args.to_leader.is_some() {
44
+ host_leader_to.unwrap_or_default().to_string()
45
+ } else if let Some(name) = args.to_name.as_deref() {
46
+ name.to_string()
47
+ } else if let Some(targets) = args.targets.as_deref() {
48
+ targets.to_string()
49
+ } else {
50
+ args.target.clone().unwrap_or_default()
51
+ };
52
+ if args.target.is_none() && supplied > 0 && args.message.is_empty() {
53
+ if args.to_name.is_some() {
54
+ return Err(CliError::Usage(
55
+ "--to-name requires a non-empty message".to_string(),
56
+ ));
57
+ }
58
+ return Err(CliError::Usage(
59
+ "send requires a non-empty message after logical TO".to_string(),
60
+ ));
61
+ }
62
+ Ok(logical_to)
63
+ }
64
+
65
+ pub(super) fn resolve_host_leader_alias(
66
+ name: &str,
67
+ ) -> Result<(String, crate::leader::registry::LeaderRegistryEntry), Value> {
68
+ let classified = crate::leader::registry::list_validated_no_gc();
69
+ let candidates = classified
70
+ .iter()
71
+ .filter(|(entry, _, _)| {
72
+ entry.delivery_name == name
73
+ || entry.qualified_name == name
74
+ || entry.stable_qualified_name == name
75
+ || entry.aliases.iter().any(|alias| alias == name)
76
+ })
77
+ .map(|(entry, _, _)| entry.clone())
78
+ .collect::<Vec<_>>();
79
+ if candidates.is_empty() {
80
+ return Err(json!({
81
+ "ok": false,
82
+ "status": "refused",
83
+ "reason": "leader_name_not_found",
84
+ "requested_name": name,
85
+ "resolved_via": "host_leader_registry",
86
+ "candidates": Vec::<Value>::new(),
87
+ "workspace_hash": null,
88
+ "stable_qualified_name": null,
89
+ "channel": "leader_mailbox",
90
+ "delivered": false,
91
+ "message_status": "queued_until_leader_attach",
92
+ "action": "run `team-agent leaders` to see registered leaders; inspect queued leader messages with `team-agent inbox`; retry with a qualified name",
93
+ "registry_stale": false,
94
+ }));
95
+ }
96
+ if candidates.len() > 1 {
97
+ let candidates = candidates
98
+ .iter()
99
+ .map(|entry| {
100
+ json!({
101
+ "name": entry.qualified_name,
102
+ "workspace": entry.workspace.display().to_string(),
103
+ "team_key": entry.team_key,
104
+ "workspace_hash": entry.workspace_hash,
105
+ "stable_qualified_name": entry.stable_qualified_name,
106
+ })
107
+ })
108
+ .collect::<Vec<_>>();
109
+ return Err(json!({
110
+ "ok": false,
111
+ "status": "refused",
112
+ "reason": "name_ambiguous",
113
+ "requested_name": name,
114
+ "resolved_via": "host_leader_registry",
115
+ "candidates": candidates,
116
+ "channel": "leader_mailbox",
117
+ "delivered": false,
118
+ "action": "run `team-agent leaders` and retry with the qualified name",
119
+ }));
120
+ }
121
+ let entry = candidates[0].clone();
122
+ let (status, reason) = crate::leader::registry::classify(&entry);
123
+ if status == "STALE" {
124
+ let team_alive = crate::state::persist::load_runtime_state(&entry.workspace)
125
+ .ok()
126
+ .and_then(|state| {
127
+ state
128
+ .get("teams")
129
+ .and_then(Value::as_object)
130
+ .and_then(|teams| teams.get(&entry.team_key))
131
+ .and_then(|team| team.get("status"))
132
+ .and_then(Value::as_str)
133
+ .map(|status| status == "alive" || status.is_empty())
134
+ })
135
+ .unwrap_or(false);
136
+ if !team_alive {
137
+ return Err(json!({
138
+ "ok": false,
139
+ "status": "refused",
140
+ "reason": "registry_stale",
141
+ "requested_name": name,
142
+ "resolved_via": "host_leader_registry",
143
+ "stale_reason": reason,
144
+ "workspace_hash": entry.workspace_hash,
145
+ "stable_qualified_name": entry.stable_qualified_name,
146
+ "channel": "leader_mailbox",
147
+ "delivered": false,
148
+ "action": "target team is not alive; run `team-agent leaders` for current state",
149
+ }));
150
+ }
151
+ }
152
+ let logical_to = format!("{}::{}/leader", entry.workspace.display(), entry.team_key);
153
+ Ok((logical_to, entry))
154
+ }
155
+
156
+ pub(super) fn decorate_host_leader_alias(
157
+ value: &mut Value,
158
+ entry: &crate::leader::registry::LeaderRegistryEntry,
159
+ ) {
160
+ let Some(object) = value.as_object_mut() else {
161
+ return;
162
+ };
163
+ object.insert("resolved_via".to_string(), json!("host_leader_registry"));
164
+ object.insert("to_leader".to_string(), json!(entry.qualified_name));
165
+ object.insert("requested_name".to_string(), json!(entry.delivery_name));
166
+ object.insert("workspace_hash".to_string(), json!(entry.workspace_hash));
167
+ object.insert(
168
+ "stable_qualified_name".to_string(),
169
+ json!(entry.stable_qualified_name),
170
+ );
171
+ }
172
+
173
+ pub(super) fn send_to_logical_to(
174
+ args: &SendArgs,
175
+ logical_to: &str,
176
+ content: &str,
177
+ ) -> Result<Value, CliError> {
178
+ let names = logical_to
179
+ .split(',')
180
+ .map(str::trim)
181
+ .filter(|name| !name.is_empty())
182
+ .collect::<Vec<_>>();
183
+ if names.is_empty() || names.len() != logical_to.split(',').count() {
184
+ return Err(CliError::Usage(
185
+ "logical TO comma-list contains an empty recipient".to_string(),
186
+ ));
187
+ }
188
+
189
+ let mut resolved = Vec::with_capacity(names.len());
190
+ for name in names {
191
+ match crate::cli::named_address::resolve_name_for_cli(
192
+ &args.workspace,
193
+ name,
194
+ args.team.as_deref(),
195
+ ) {
196
+ Ok((recipient, _transport)) => resolved.push(recipient),
197
+ Err(mut error) => {
198
+ adapt_positional_bare_error(args, name, &mut error);
199
+ if matches!(
200
+ error.kind,
201
+ crate::cli::named_address::NamedAddressErrorKind::StateNotFound
202
+ ) {
203
+ return Ok(resolution_refusal_json(&error, logical_to, content, args));
204
+ }
205
+ if resolved.is_empty() && !logical_to.contains(',') {
206
+ if let Some(value) = maybe_enqueue_offline_leader_mailbox(
207
+ &args.workspace,
208
+ name,
209
+ content,
210
+ args.sender.as_str(),
211
+ args.task.as_deref(),
212
+ &error,
213
+ )? {
214
+ return Ok(value);
215
+ }
216
+ }
217
+ if args.json {
218
+ return Ok(error.to_json());
219
+ }
220
+ return Err(CliError::Usage(error.n38_message()));
221
+ }
222
+ }
223
+ }
224
+
225
+ if resolved.len() == 1 {
226
+ return send_to_resolved_name(args, &resolved[0], content);
227
+ }
228
+
229
+ let first = &resolved[0];
230
+ let one_scope = resolved.iter().all(|recipient| {
231
+ recipient.target_workspace == first.target_workspace && recipient.team_key == first.team_key
232
+ });
233
+ if one_scope {
234
+ let recipients = resolved
235
+ .iter()
236
+ .map(logical_recipient_id)
237
+ .collect::<Result<Vec<_>, _>>()?;
238
+ let target = MessageTarget::Fanout(recipients);
239
+ return persist_resolved_target(args, first, &target, content);
240
+ }
241
+
242
+ let mut results = Vec::with_capacity(resolved.len());
243
+ for recipient in &resolved {
244
+ results.push(send_to_resolved_name(args, recipient, content)?);
245
+ }
246
+ let ok = results
247
+ .iter()
248
+ .all(|value| value.get("ok").and_then(Value::as_bool) == Some(true));
249
+ let message_id = results
250
+ .iter()
251
+ .rev()
252
+ .find_map(|value| value.get("message_id").and_then(Value::as_str))
253
+ .map(str::to_string);
254
+ Ok(json!({
255
+ "ok": ok,
256
+ "status": if ok { "fanout_delivered" } else { "fanout_partial" },
257
+ "delivery_status": if ok { "pending" } else { "fanout_partial" },
258
+ "delivered": false,
259
+ "target": logical_to.split(',').map(str::trim).collect::<Vec<_>>(),
260
+ "content_length_bytes": content.len(),
261
+ "sender": args.sender,
262
+ "message_id": message_id,
263
+ "results": results,
264
+ }))
265
+ }
266
+
267
+ pub(super) fn resolution_refusal_json(
268
+ error: &crate::cli::named_address::NamedAddressError,
269
+ logical_to: &str,
270
+ content: &str,
271
+ args: &SendArgs,
272
+ ) -> Value {
273
+ let mut value = error.to_json();
274
+ if let Some(object) = value.as_object_mut() {
275
+ object.insert("delivery_status".to_string(), json!("refused"));
276
+ object.insert("delivered".to_string(), json!(false));
277
+ object.insert("target".to_string(), json!(logical_to));
278
+ object.insert("agent_id".to_string(), json!(logical_to));
279
+ object.insert("content_length_bytes".to_string(), json!(content.len()));
280
+ object.insert("sender".to_string(), json!(args.sender));
281
+ object.insert("message_id".to_string(), Value::Null);
282
+ object.insert("message_status".to_string(), json!("refused"));
283
+ object.insert("verification".to_string(), Value::Null);
284
+ object.insert("stage".to_string(), Value::Null);
285
+ object.insert("channel".to_string(), Value::Null);
286
+ }
287
+ value
288
+ }
289
+
290
+ pub(super) fn adapt_positional_bare_error(
291
+ args: &SendArgs,
292
+ name: &str,
293
+ error: &mut crate::cli::named_address::NamedAddressError,
294
+ ) {
295
+ if args.target.as_deref() != Some(name)
296
+ || args.team.is_none()
297
+ || name.contains('/')
298
+ || name.contains(':')
299
+ || name.contains(',')
300
+ {
301
+ return;
302
+ }
303
+ error.requested_name = Some(name.to_string());
304
+ for candidate in &mut error.candidates {
305
+ let agent_id = candidate
306
+ .get("agent_id")
307
+ .and_then(Value::as_str)
308
+ .map(str::to_string);
309
+ if let (Some(object), Some(agent_id)) = (candidate.as_object_mut(), agent_id) {
310
+ object.insert("name".to_string(), json!(agent_id));
311
+ }
312
+ }
313
+ error.suggested_name = error
314
+ .suggested_name
315
+ .as_deref()
316
+ .and_then(|suggested| suggested.rsplit('/').next())
317
+ .map(str::to_string);
318
+ if let Some(suggested) = error.suggested_name.as_deref() {
319
+ error.action = format!("Did you mean `{suggested}`? Retry with `{suggested}` as TO.");
320
+ }
321
+ }
322
+
323
+ pub(super) fn logical_recipient_id(
324
+ resolved: &crate::cli::named_address::ResolvedNamedAddress,
325
+ ) -> Result<String, CliError> {
326
+ match resolved.target_kind {
327
+ crate::cli::named_address::NamedTargetKind::Worker => resolved
328
+ .agent_id
329
+ .clone()
330
+ .ok_or_else(|| CliError::Runtime("resolved worker is missing agent id".to_string())),
331
+ crate::cli::named_address::NamedTargetKind::Leader => Ok("leader".to_string()),
332
+ crate::cli::named_address::NamedTargetKind::SessionWindow => Err(CliError::Usage(
333
+ "named session/window delivery is sunset; use a logical agent or leader name"
334
+ .to_string(),
335
+ )),
336
+ }
337
+ }
338
+
339
+ pub(super) fn send_to_resolved_name(
340
+ args: &SendArgs,
341
+ resolved: &crate::cli::named_address::ResolvedNamedAddress,
342
+ content: &str,
343
+ ) -> Result<Value, CliError> {
344
+ let recipient = logical_recipient_id(resolved)?;
345
+ if let Some(warning) = &resolved.warning {
346
+ eprintln!("warning: {warning}");
347
+ }
348
+ let target = MessageTarget::Single(recipient);
349
+ let mut value = persist_resolved_target(args, resolved, &target, content)?;
350
+ if args.to_name.is_some() || args.to_leader.is_some() {
351
+ if let Some(obj) = value.as_object_mut() {
352
+ obj.insert("to_name".to_string(), json!(resolved.raw_name));
353
+ obj.insert(
354
+ "target_workspace".to_string(),
355
+ json!(resolved.target_workspace.display().to_string()),
356
+ );
357
+ obj.insert("team_key".to_string(), json!(resolved.team_key));
358
+ }
359
+ }
360
+ Ok(value)
361
+ }