@team-agent/installer 0.5.92 → 0.5.94

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.
@@ -68,13 +68,16 @@ use crate::transport::{
68
68
  tmux_spawn_argv, AttachOutcome, BackendKind, BusySignalKind, CaptureRange,
69
69
  CaptureSampleOutcome, CapturedText, InjectPayload, InjectReport, InjectStage,
70
70
  InjectVerification, InputSurfaceProbe, Key, PaneField, PaneId, PaneInfo, PaneMode,
71
- QueryOutcome, SessionName, SetEnvOutcome, SpawnResult, SubmitAttemptObservation,
72
- SubmitConsumptionReason, SubmitDiagnostics, SubmitObserver, SubmitVerification, Target,
73
- Transport, TransportError, TurnVerification, WindowName,
71
+ QueryOutcome, SessionName, SessionOwner, SetEnvOutcome, SpawnResult,
72
+ SubmitAttemptObservation, SubmitConsumptionReason, SubmitDiagnostics, SubmitObserver,
73
+ SubmitVerification, Target, Transport, TransportError, TurnVerification, WindowName,
74
74
  };
75
75
 
76
76
  pub const PANE_BINDING_NONCE_METADATA_KEY: &str = "TEAM_AGENT_PANE_BINDING_NONCE";
77
77
  const TMUX_PANE_BINDING_NONCE_OPTION: &str = "@team_agent_pane_binding_nonce";
78
+ const TMUX_SESSION_OWNER_WORKSPACE_OPTION: &str = "@team_agent_owner_workspace";
79
+ const TMUX_SESSION_OWNER_TEAM_OPTION: &str = "@team_agent_owner_team";
80
+ const TMUX_SESSION_OWNER_GENERATION_OPTION: &str = "@team_agent_owner_generation";
78
81
 
79
82
  /// Result of running an external command — the typed output of the OS edge.
80
83
  #[derive(Debug, Clone)]
@@ -351,6 +354,87 @@ impl TmuxBackend {
351
354
  }
352
355
  }
353
356
 
357
+ fn set_session_owner_marker(
358
+ &self,
359
+ session: &SessionName,
360
+ workspace: &Path,
361
+ team: &str,
362
+ generation: &str,
363
+ ) -> Result<(), TransportError> {
364
+ let workspace = workspace
365
+ .canonicalize()
366
+ .unwrap_or_else(|_| workspace.to_path_buf())
367
+ .to_string_lossy()
368
+ .into_owned();
369
+ for (option, value) in [
370
+ (TMUX_SESSION_OWNER_WORKSPACE_OPTION, workspace),
371
+ (TMUX_SESSION_OWNER_TEAM_OPTION, team.to_string()),
372
+ (TMUX_SESSION_OWNER_GENERATION_OPTION, generation.to_string()),
373
+ ] {
374
+ let argv = self.tmux_argv(&[
375
+ "tmux".to_string(),
376
+ "set-option".to_string(),
377
+ "-t".to_string(),
378
+ session.as_str().to_string(),
379
+ option.to_string(),
380
+ value,
381
+ ]);
382
+ let output = self.runner.run(&argv)?;
383
+ if !output.success {
384
+ return Err(subprocess_error(argv, output));
385
+ }
386
+ }
387
+ Ok(())
388
+ }
389
+
390
+ fn read_session_owner_option(
391
+ &self,
392
+ session: &SessionName,
393
+ option: &str,
394
+ ) -> Result<Option<String>, TransportError> {
395
+ let argv = self.tmux_argv(&[
396
+ "tmux".to_string(),
397
+ "show-options".to_string(),
398
+ "-qv".to_string(),
399
+ "-t".to_string(),
400
+ session.as_str().to_string(),
401
+ option.to_string(),
402
+ ]);
403
+ let output = self.runner.run(&argv)?;
404
+ if !output.success {
405
+ return Ok(None);
406
+ }
407
+ let value = output.stdout.trim();
408
+ Ok((!value.is_empty()).then(|| value.to_string()))
409
+ }
410
+
411
+ fn read_session_owner_marker(
412
+ &self,
413
+ session: &SessionName,
414
+ ) -> Result<Option<SessionOwner>, TransportError> {
415
+ let Some(workspace) = self.read_session_owner_option(
416
+ session,
417
+ TMUX_SESSION_OWNER_WORKSPACE_OPTION,
418
+ )? else {
419
+ return Ok(None);
420
+ };
421
+ let Some(team) = self.read_session_owner_option(session, TMUX_SESSION_OWNER_TEAM_OPTION)?
422
+ else {
423
+ return Ok(None);
424
+ };
425
+ let Some(generation) = self.read_session_owner_option(
426
+ session,
427
+ TMUX_SESSION_OWNER_GENERATION_OPTION,
428
+ )? else {
429
+ return Ok(None);
430
+ };
431
+ Ok(Some(SessionOwner {
432
+ workspace,
433
+ team,
434
+ generation,
435
+ }))
436
+ }
437
+
354
438
  /// ---
355
439
  /// purpose: 把 leader 认领的绑定 nonce 写进 pane 级 tmux 用户选项,给「这个 pane 实例是不是我绑的那个」留可核凭据
356
440
  /// params:
@@ -2986,6 +3070,32 @@ impl Transport for TmuxBackend {
2986
3070
  .map(|endpoint| endpoint.as_endpoint().to_string())
2987
3071
  }
2988
3072
 
3073
+ fn set_session_owner(
3074
+ &self,
3075
+ session: &SessionName,
3076
+ workspace: &Path,
3077
+ team: &str,
3078
+ ) -> Result<(), TransportError> {
3079
+ self.set_session_owner_marker(session, workspace, team, session.as_str())
3080
+ }
3081
+
3082
+ fn set_session_owner_with_generation(
3083
+ &self,
3084
+ session: &SessionName,
3085
+ workspace: &Path,
3086
+ team: &str,
3087
+ generation: &str,
3088
+ ) -> Result<(), TransportError> {
3089
+ self.set_session_owner_marker(session, workspace, team, generation)
3090
+ }
3091
+
3092
+ fn session_owner(
3093
+ &self,
3094
+ session: &SessionName,
3095
+ ) -> Result<Option<SessionOwner>, TransportError> {
3096
+ self.read_session_owner_marker(session)
3097
+ }
3098
+
2989
3099
  fn spawn_first(
2990
3100
  &self,
2991
3101
  session: &SessionName,
@@ -55,6 +55,7 @@ struct OfflineState {
55
55
  /// 0.4.x (CR C-3 / CR C-5): pre-staged `query(PaneField::PaneCurrentCommand)`
56
56
  /// answer keyed by pane id. Used by leader provider health tests.
57
57
  pane_current_commands: BTreeMap<String, String>,
58
+ session_owner: Option<super::SessionOwner>,
58
59
  }
59
60
 
60
61
  impl Default for OfflineState {
@@ -81,6 +82,7 @@ impl Default for OfflineState {
81
82
  capture_text: BTreeMap::new(),
82
83
  capture_ranges: Vec::new(),
83
84
  pane_current_commands: BTreeMap::new(),
85
+ session_owner: None,
84
86
  }
85
87
  }
86
88
  }
@@ -159,6 +161,27 @@ impl OfflineTransport {
159
161
  self
160
162
  }
161
163
 
164
+ pub fn with_session_owner(
165
+ self,
166
+ workspace: &Path,
167
+ team: impl Into<String>,
168
+ generation: impl Into<String>,
169
+ ) -> Self {
170
+ let workspace = workspace
171
+ .canonicalize()
172
+ .unwrap_or_else(|_| workspace.to_path_buf())
173
+ .to_string_lossy()
174
+ .into_owned();
175
+ self.with_state(|state| {
176
+ state.session_owner = Some(super::SessionOwner {
177
+ workspace,
178
+ team: team.into(),
179
+ generation: generation.into(),
180
+ });
181
+ });
182
+ self
183
+ }
184
+
162
185
  /// U1-B: stage a `list_targets()` error to model tmux server jitter. While set,
163
186
  /// every call returns `Err(TransportError::MuxUnavailable{detail})`. Pass an
164
187
  /// empty string to clear via re-builder if needed.
@@ -532,6 +555,13 @@ impl Transport for OfflineTransport {
532
555
  }))
533
556
  }
534
557
 
558
+ fn session_owner(
559
+ &self,
560
+ _session: &SessionName,
561
+ ) -> Result<Option<super::SessionOwner>, TransportError> {
562
+ Ok(self.with_state(|state| state.session_owner.clone()))
563
+ }
564
+
535
565
  fn list_windows(&self, _session: &SessionName) -> Result<Vec<WindowName>, TransportError> {
536
566
  Ok(self.with_state(|state| {
537
567
  state.calls.push("list_windows");
@@ -325,6 +325,13 @@ pub struct SpawnResult {
325
325
 
326
326
  /// 全局枚举的一行(身份地基)。`leader_env` 在 tmux 后端靠反向读进程 env,在
327
327
  /// WezTerm/ConPTY 后端靠正向登记表投影(§4a)。
328
+ #[derive(Debug, Clone, PartialEq, Eq)]
329
+ pub struct SessionOwner {
330
+ pub workspace: String,
331
+ pub team: String,
332
+ pub generation: String,
333
+ }
334
+
328
335
  #[derive(Debug, Clone, PartialEq, Eq)]
329
336
  pub struct PaneInfo {
330
337
  pub pane_id: PaneId,
@@ -1000,6 +1007,40 @@ pub trait Transport: Send + Sync {
1000
1007
  /// wezterm=`cli list --format json` + 正向登记表;conpty=daemon 内存表(仅自有,§4a)。
1001
1008
  fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError>;
1002
1009
 
1010
+ /// Write an owner marker at the session scope after a new session is created.
1011
+ /// Backends without a session-local metadata primitive may no-op, but then
1012
+ /// shutdown must treat the missing marker as unknown and refuse destructive work.
1013
+ fn set_session_owner(
1014
+ &self,
1015
+ _session: &SessionName,
1016
+ _workspace: &Path,
1017
+ _team: &str,
1018
+ ) -> Result<(), TransportError> {
1019
+ Ok(())
1020
+ }
1021
+
1022
+ /// Write owner metadata including the launch generation when the backend
1023
+ /// can persist one. The default preserves compatibility for non-tmux
1024
+ /// transports, which remain Unknown on reads.
1025
+ fn set_session_owner_with_generation(
1026
+ &self,
1027
+ session: &SessionName,
1028
+ workspace: &Path,
1029
+ team: &str,
1030
+ _generation: &str,
1031
+ ) -> Result<(), TransportError> {
1032
+ self.set_session_owner(session, workspace, team)
1033
+ }
1034
+
1035
+ /// Read the session-local owner marker. `None` is an unknown/legacy session,
1036
+ /// never proof that the caller owns it.
1037
+ fn session_owner(
1038
+ &self,
1039
+ _session: &SessionName,
1040
+ ) -> Result<Option<SessionOwner>, TransportError> {
1041
+ Ok(None)
1042
+ }
1043
+
1003
1044
  fn has_session(&self, session: &SessionName) -> Result<bool, TransportError>;
1004
1045
 
1005
1046
  fn list_windows(&self, session: &SessionName) -> Result<Vec<WindowName>, TransportError>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.5.92",
3
+ "version": "0.5.94",
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.92",
24
- "@team-agent/cli-darwin-x64": "0.5.92",
25
- "@team-agent/cli-linux-x64": "0.5.92"
23
+ "@team-agent/cli-darwin-arm64": "0.5.94",
24
+ "@team-agent/cli-darwin-x64": "0.5.94",
25
+ "@team-agent/cli-linux-x64": "0.5.94"
26
26
  },
27
27
  "scripts": {
28
28
  "postinstall": "node npm/bincheck.mjs",