@team-agent/installer 0.5.72 → 0.5.74

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 (37) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +19 -13
  4. package/crates/team-agent/src/cli/emit.rs +6 -3
  5. package/crates/team-agent/src/cli/mod.rs +151 -0
  6. package/crates/team-agent/src/cli/tests/leader_watch.rs +28 -0
  7. package/crates/team-agent/src/cli/tests/run_delegation.rs +3 -0
  8. package/crates/team-agent/src/cli/types.rs +6 -0
  9. package/crates/team-agent/src/compiler.rs +14 -1
  10. package/crates/team-agent/src/leader/lease.rs +106 -21
  11. package/crates/team-agent/src/leader/registry.rs +0 -89
  12. package/crates/team-agent/src/leader/start.rs +8 -0
  13. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +43 -7
  14. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +457 -73
  15. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +16 -6
  16. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -0
  17. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +5 -3
  18. package/crates/team-agent/src/lifecycle/restart/common.rs +1 -11
  19. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +60 -0
  20. package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +1 -1
  21. package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +71 -1
  22. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +34 -1
  23. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +25 -23
  24. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +396 -3
  25. package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +3 -1
  26. package/crates/team-agent/src/messaging/delivery.rs +26 -8
  27. package/crates/team-agent/src/messaging/tests/runtime.rs +234 -0
  28. package/crates/team-agent/src/model/spec.rs +81 -7
  29. package/crates/team-agent/src/provider/adapters/pi.rs +26 -6
  30. package/crates/team-agent/src/state/persist.rs +272 -3
  31. package/crates/team-agent/src/state/repository.rs +9 -2
  32. package/crates/team-agent/src/tmux_backend/tests.rs +64 -0
  33. package/crates/team-agent/src/tmux_backend.rs +57 -0
  34. package/package.json +4 -4
  35. package/skills/team-agent/SKILL.md +4 -1
  36. package/skills/team-agent/command-coverage.json +21 -0
  37. package/skills/team-agent/references/team-agent-operator.md +9 -2
@@ -243,12 +243,6 @@ pub fn register_binding_from_state_best_effort(
243
243
  );
244
244
  let event_log = crate::event_log::EventLog::new(workspace);
245
245
  let write_result = write_entry_best_effort(&entry);
246
- // Fresh quick-start is acquisition-only: it must never displace another
247
- // same-pane row. Its caller performs a validated-LIVE check before this
248
- // write; manual/restart ownership paths keep existing behavior.
249
- if write_result.is_some() && source != "quick-start" {
250
- let _ = mark_displaced_same_pane_entries_unbound(&entry);
251
- }
252
246
  let status = if let Some(path) = &write_result {
253
247
  let _ = event_log.write(
254
248
  EVENT_REGISTERED,
@@ -281,68 +275,6 @@ pub fn register_binding_from_state_best_effort(
281
275
  })
282
276
  }
283
277
 
284
- /// Stored registry status after another workspace claimed the same pane.
285
- pub const REGISTRY_STATUS_UNBOUND: &str = "unbound";
286
-
287
- /// When workspace B claims a pane, every other attached registry row that
288
- /// still names that pane is no longer deliverable. Mark those rows unbound.
289
- /// Does not change pane-side authorization storage.
290
- pub fn mark_displaced_same_pane_entries_unbound(winner: &LeaderRegistryEntry) -> Vec<PathBuf> {
291
- let Some(winner_pane) = winner
292
- .channel
293
- .get("pane_id")
294
- .and_then(Value::as_str)
295
- .filter(|pane| !pane.is_empty())
296
- else {
297
- return Vec::new();
298
- };
299
- let winner_socket = winner
300
- .channel
301
- .get("tmux_socket")
302
- .and_then(Value::as_str)
303
- .filter(|socket| !socket.is_empty());
304
- let mut displaced = Vec::new();
305
- for (path, mut entry) in read_all_entries() {
306
- if entry.workspace_hash == winner.workspace_hash && entry.team_key == winner.team_key {
307
- continue;
308
- }
309
- if entry.status != "attached" {
310
- continue;
311
- }
312
- let Some(pane) = entry
313
- .channel
314
- .get("pane_id")
315
- .and_then(Value::as_str)
316
- .filter(|pane| !pane.is_empty())
317
- else {
318
- continue;
319
- };
320
- if pane != winner_pane {
321
- continue;
322
- }
323
- let Some(socket) = entry
324
- .channel
325
- .get("tmux_socket")
326
- .and_then(Value::as_str)
327
- .filter(|socket| !socket.is_empty())
328
- else {
329
- continue;
330
- };
331
- let Some(winner_socket) = winner_socket else {
332
- continue;
333
- };
334
- if socket != winner_socket {
335
- continue;
336
- }
337
- entry.status = REGISTRY_STATUS_UNBOUND.to_string();
338
- entry.updated_at = chrono::Utc::now().to_rfc3339();
339
- if write_entry_best_effort(&entry).is_some() {
340
- displaced.push(path);
341
- }
342
- }
343
- displaced
344
- }
345
-
346
278
  fn entry_filename(entry: &LeaderRegistryEntry) -> String {
347
279
  format!("{}__{}.json", entry.workspace_hash, entry.team_key)
348
280
  }
@@ -546,27 +478,6 @@ pub fn list_validated_with_gc() -> Vec<(LeaderRegistryEntry, &'static str, Optio
546
478
  out
547
479
  }
548
480
 
549
- /// Refuse a fresh quick-start binding when the caller pane already backs a
550
- /// canonically LIVE receiver for another workspace/team. This is read-only:
551
- /// quick-start never displaces or takes over an existing binding.
552
- #[must_use]
553
- pub(crate) fn live_same_pane_binding_elsewhere(
554
- pane_id: &str,
555
- workspace: &Path,
556
- team_key: &str,
557
- ) -> bool {
558
- let workspace = std::fs::canonicalize(workspace).unwrap_or_else(|_| workspace.to_path_buf());
559
- list_validated_no_gc().into_iter().any(|(entry, status, _)| {
560
- status == "LIVE"
561
- && entry
562
- .channel
563
- .get("pane_id")
564
- .and_then(Value::as_str)
565
- == Some(pane_id)
566
- && (entry.workspace != workspace || entry.team_key != team_key)
567
- })
568
- }
569
-
570
481
  /// Same as `list_validated_with_gc` but preserves all entries. `send
571
482
  /// --to-leader` needs to see stale entries so it can refuse with
572
483
  /// `registry_stale` (and never silently fall through to
@@ -162,6 +162,7 @@ pub(crate) fn prepare_leader_start_with_nested_attach(
162
162
  std::env::var_os("TMUX").is_some(),
163
163
  managed_client_attach_mode,
164
164
  managed_exec_provider,
165
+ allow_nested_attach,
165
166
  )?;
166
167
  Ok(PreparedLeaderStart {
167
168
  plan,
@@ -196,6 +197,7 @@ pub(crate) fn leader_start_plan_after_ambient_authority_check(
196
197
  in_tmux,
197
198
  managed_client_attach_mode,
198
199
  false,
200
+ false,
199
201
  )
200
202
  }
201
203
 
@@ -210,6 +212,7 @@ fn leader_start_plan_with_ambient_authority(
210
212
  in_tmux: bool,
211
213
  managed_client_attach_mode: Option<ManagedClientAttachMode>,
212
214
  managed_exec_provider: bool,
215
+ allow_nested_attach: bool,
213
216
  ) -> Result<LeaderStartPlan, LeaderError> {
214
217
  if attach_session.is_some() && !confirm_attach {
215
218
  return Err(LeaderError::Start(
@@ -327,6 +330,11 @@ fn leader_start_plan_with_ambient_authority(
327
330
  tool_categories: &tools,
328
331
  team_mcp_tools: &team_mcp_tools,
329
332
  mcp_config: &mcp_config,
333
+ session_scope: crate::lifecycle::launch::pi_mcp::pi_leader_session_scope(
334
+ provider_args,
335
+ external_path,
336
+ allow_nested_attach,
337
+ ),
330
338
  },
331
339
  )
332
340
  .map_err(|error| LeaderError::Start(error.to_string()))?
@@ -816,6 +816,27 @@ pub(crate) fn resolve_pi_executable_chain(
816
816
  Ok((chain, models))
817
817
  }
818
818
 
819
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
820
+ pub(crate) enum PiSessionScope {
821
+ Isolated,
822
+ NativeDefault,
823
+ }
824
+
825
+ /// Zero-argument managed Pi is the interactive leader entry. Only that route
826
+ /// may use Pi's cwd-keyed native session root; every explicit or worker route
827
+ /// remains Team Agent session-isolated.
828
+ pub(crate) fn pi_leader_session_scope(
829
+ provider_args: &[String],
830
+ external_path: bool,
831
+ allow_nested_attach: bool,
832
+ ) -> PiSessionScope {
833
+ if provider_args.is_empty() && !external_path && !allow_nested_attach {
834
+ PiSessionScope::NativeDefault
835
+ } else {
836
+ PiSessionScope::Isolated
837
+ }
838
+ }
839
+
819
840
  pub(crate) struct PiMaterializeRequest<'a> {
820
841
  pub workspace: &'a Path,
821
842
  pub team_id: &'a str,
@@ -826,11 +847,13 @@ pub(crate) struct PiMaterializeRequest<'a> {
826
847
  pub tool_categories: &'a [&'a str],
827
848
  pub team_mcp_tools: &'a [&'a str],
828
849
  pub mcp_config: &'a McpConfig,
850
+ pub session_scope: PiSessionScope,
829
851
  }
830
852
 
831
853
  /// ---
832
854
  /// purpose: 共享物化 Pi leader 与 TeamMate 的 wrapper、UUID 和 CommandPlan
833
- /// returns: fresh Pi CommandPlan,含 expected UUID 与 seat session root
855
+ /// returns: fresh Pi CommandPlan;isolated seats carry their session root, while the
856
+ /// interactive leader preserves Pi's native default root
834
857
  /// errors: resolver、exact model、wrapper 或 argv 构造失败时返回 ProviderError
835
858
  /// ---
836
859
  pub(crate) fn materialize_pi_plan(
@@ -863,8 +886,18 @@ fn materialize_pi_plan_with_session(
863
886
  .map(|model| select_exact_pi_model(&catalog, model))
864
887
  .transpose()?;
865
888
  let paths = pi_seat_paths(request.workspace, request.team_id, request.agent_id);
889
+ let session_dir = match request.session_scope {
890
+ PiSessionScope::Isolated => Some(paths.sessions.as_path()),
891
+ PiSessionScope::NativeDefault => None,
892
+ };
866
893
  let resume = if let Some((session_id, session_path, spawn_cwd)) = resume {
867
- let session_root = std::fs::canonicalize(&paths.sessions).map_err(|error| {
894
+ let session_dir = session_dir.ok_or_else(|| {
895
+ ProviderError::ResumeUnavailable(format!(
896
+ "Pi session root is unavailable {}",
897
+ paths.sessions.display()
898
+ ))
899
+ })?;
900
+ let session_root = std::fs::canonicalize(session_dir).map_err(|error| {
868
901
  ProviderError::ResumeUnavailable(format!(
869
902
  "Pi session root is unavailable {}: {error}",
870
903
  paths.sessions.display()
@@ -885,8 +918,11 @@ fn materialize_pi_plan_with_session(
885
918
  let _ = spawn_cwd;
886
919
  Some((session_id, exact_path))
887
920
  } else {
888
- std::fs::create_dir_all(&paths.sessions)
889
- .map_err(|error| ProviderError::Io(format!("{}: {error}", paths.sessions.display())))?;
921
+ if let Some(session_dir) = session_dir {
922
+ std::fs::create_dir_all(session_dir).map_err(|error| {
923
+ ProviderError::Io(format!("{}: {error}", session_dir.display()))
924
+ })?;
925
+ }
890
926
  None
891
927
  };
892
928
  let candidate = candidate_from_mcp_config(request.mcp_config)?;
@@ -908,7 +944,7 @@ fn materialize_pi_plan_with_session(
908
944
  effort: request.effort,
909
945
  system_prompt: request.system_prompt,
910
946
  tool_categories: request.tool_categories,
911
- session_dir: &paths.sessions,
947
+ session_dir,
912
948
  session: PiSessionSelector::Resume { path: &exact_path },
913
949
  agent_id: request.agent_id,
914
950
  })?;
@@ -922,7 +958,7 @@ fn materialize_pi_plan_with_session(
922
958
  effort: request.effort,
923
959
  system_prompt: request.system_prompt,
924
960
  tool_categories: request.tool_categories,
925
- session_dir: &paths.sessions,
961
+ session_dir,
926
962
  session: PiSessionSelector::Fresh {
927
963
  session_id: session_id.as_str(),
928
964
  },
@@ -933,7 +969,7 @@ fn materialize_pi_plan_with_session(
933
969
  Ok(CommandPlan {
934
970
  argv,
935
971
  expected_session_id,
936
- provider_projects_root: Some(paths.sessions),
972
+ provider_projects_root: session_dir.map(Path::to_path_buf),
937
973
  managed_mcp_config: false,
938
974
  })
939
975
  }