@team-agent/installer 0.5.51 → 0.5.53

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 (79) 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 +17 -0
  5. package/crates/team-agent/src/cli/mod.rs +155 -4
  6. package/crates/team-agent/src/cli/named_address.rs +35 -67
  7. package/crates/team-agent/src/cli/spec.rs +3 -0
  8. package/crates/team-agent/src/cli/tests/main_preserved.rs +1 -0
  9. package/crates/team-agent/src/cli/types.rs +11 -0
  10. package/crates/team-agent/src/diagnose/orphans.rs +24 -3
  11. package/crates/team-agent/src/kill_audit.rs +67 -0
  12. package/crates/team-agent/src/leader/lease.rs +526 -73
  13. package/crates/team-agent/src/leader/rediscover/tests.rs +3 -0
  14. package/crates/team-agent/src/leader/rediscover.rs +6 -0
  15. package/crates/team-agent/src/leader/start.rs +144 -23
  16. package/crates/team-agent/src/leader/tests/idle.rs +3 -0
  17. package/crates/team-agent/src/leader/types.rs +6 -0
  18. package/crates/team-agent/src/lib.rs +1 -0
  19. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +1 -1
  20. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +12 -1
  21. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +4 -0
  22. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +106 -0
  23. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +216 -208
  24. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +36 -0
  25. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +238 -0
  26. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +101 -5
  27. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +1 -1
  28. package/crates/team-agent/src/lifecycle/launch/role_source.rs +170 -0
  29. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +8 -8
  30. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +1 -1
  31. package/crates/team-agent/src/lifecycle/launch.rs +14 -2
  32. package/crates/team-agent/src/lifecycle/restart/agent.rs +20 -4
  33. package/crates/team-agent/src/lifecycle/restart/common.rs +12 -0
  34. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +113 -4
  35. package/crates/team-agent/src/lifecycle/restart/remove.rs +73 -10
  36. package/crates/team-agent/src/lifecycle/restart.rs +25 -1
  37. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +441 -12
  38. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +20 -2
  39. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +1 -0
  40. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +3 -1
  41. package/crates/team-agent/src/lifecycle/types.rs +17 -0
  42. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +40 -5
  43. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -1
  44. package/crates/team-agent/src/mcp_server/tests/tools.rs +18 -1
  45. package/crates/team-agent/src/mcp_server/tests/wire.rs +243 -167
  46. package/crates/team-agent/src/mcp_server/tests.rs +11 -4
  47. package/crates/team-agent/src/mcp_server/tools.rs +18 -4
  48. package/crates/team-agent/src/mcp_server/types.rs +3 -0
  49. package/crates/team-agent/src/mcp_server/wire.rs +30 -7
  50. package/crates/team-agent/src/messaging/delivery.rs +126 -147
  51. package/crates/team-agent/src/messaging/leader_channel.rs +198 -0
  52. package/crates/team-agent/src/messaging/leader_receiver.rs +68 -21
  53. package/crates/team-agent/src/messaging/mod.rs +5 -0
  54. package/crates/team-agent/src/messaging/tests/leader_channel.rs +169 -0
  55. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  56. package/crates/team-agent/src/messaging/tests/runtime.rs +34 -23
  57. package/crates/team-agent/src/provider/adapter.rs +54 -13
  58. package/crates/team-agent/src/provider/adapters/claude_fork.rs +122 -0
  59. package/crates/team-agent/src/provider/adapters/copilot_fork.rs +306 -0
  60. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  61. package/crates/team-agent/src/provider/session/capture.rs +321 -52
  62. package/crates/team-agent/src/provider/session/context_fork.rs +499 -0
  63. package/crates/team-agent/src/provider/session/mod.rs +6 -0
  64. package/crates/team-agent/src/provider/session_scan/claude.rs +1 -1
  65. package/crates/team-agent/src/provider/session_scan/codex.rs +144 -27
  66. package/crates/team-agent/src/provider/session_scan/common.rs +39 -6
  67. package/crates/team-agent/src/provider/session_scan/copilot.rs +47 -3
  68. package/crates/team-agent/src/provider/session_scan.rs +3 -3
  69. package/crates/team-agent/src/provider/tests/copilot_fork.rs +191 -0
  70. package/crates/team-agent/src/provider/tests.rs +1 -0
  71. package/crates/team-agent/src/state/persist.rs +282 -20
  72. package/crates/team-agent/src/state/projection.rs +245 -30
  73. package/crates/team-agent/src/state/repository/intent.rs +13 -0
  74. package/crates/team-agent/src/state/repository.rs +36 -12
  75. package/crates/team-agent/src/state/selector.rs +9 -18
  76. package/crates/team-agent/src/tmux_backend/tests.rs +51 -16
  77. package/crates/team-agent/src/tmux_backend.rs +29 -2
  78. package/package.json +4 -4
  79. package/skills/team-agent/SKILL.md +8 -3
@@ -12,7 +12,9 @@ use std::os::unix::net::UnixListener;
12
12
  use std::path::Path;
13
13
  use std::sync::{Arc, Mutex};
14
14
 
15
- use super::{CommandOutput, CommandRunner, RealCommandRunner, TmuxBackend};
15
+ use super::{
16
+ CommandOutput, CommandRunner, RealCommandRunner, TmuxBackend, PANE_BINDING_NONCE_METADATA_KEY,
17
+ };
16
18
  use crate::model::enums::PaneLiveness;
17
19
  use crate::transport::{
18
20
  normalize_capture, tmux_capture_argv, tmux_query_argv, tmux_send_keys_argv, tmux_spawn_argv,
@@ -395,10 +397,7 @@ fn spawn_first_frames_via_new_session_builder_and_parses_pane_id() {
395
397
  let pane_inventory = "%3\tteamsess\t0\tw1\t0\t/dev/ttys003\tnode\t1\t/work/dir\t1\t0\t123\n";
396
398
  let (be, rec) = backend_with(
397
399
  MockResp::Out(ok("")),
398
- vec![
399
- MockResp::Out(ok("%3\n")),
400
- MockResp::Out(ok(pane_inventory)),
401
- ],
400
+ vec![MockResp::Out(ok("%3\n")), MockResp::Out(ok(pane_inventory))],
402
401
  );
403
402
  let s = SessionName::new("teamsess");
404
403
  let w = WindowName::new("w1");
@@ -443,10 +442,7 @@ fn spawn_into_frames_via_new_window_builder() {
443
442
  let pane_inventory = "%4\tteamsess\t1\tw2\t0\t/dev/ttys004\tnode\t1\t/work/dir\t1\t0\t124\n";
444
443
  let (be, rec) = backend_with(
445
444
  MockResp::Out(ok("")),
446
- vec![
447
- MockResp::Out(ok("%4\n")),
448
- MockResp::Out(ok(pane_inventory)),
449
- ],
445
+ vec![MockResp::Out(ok("%4\n")), MockResp::Out(ok(pane_inventory))],
450
446
  );
451
447
  let s = SessionName::new("teamsess");
452
448
  let w = WindowName::new("w2");
@@ -518,10 +514,7 @@ fn spawn_with_command_refuses_and_rolls_back_spawn_pane_owned_by_other_window()
518
514
  let pane_inventory = "%5\tteamsess\t1\tw2\t0\t/dev/ttys005\tnode\t1\t/work/dir\t1\t0\t125\n";
519
515
  let (be, rec) = backend_with(
520
516
  MockResp::Out(ok("")),
521
- vec![
522
- MockResp::Out(ok("%5\n")),
523
- MockResp::Out(ok(pane_inventory)),
524
- ],
517
+ vec![MockResp::Out(ok("%5\n")), MockResp::Out(ok(pane_inventory))],
525
518
  );
526
519
  let err = be
527
520
  .spawn_into(
@@ -1791,9 +1784,9 @@ fn query_single_field_argv_and_nonzero_maps_to_none() {
1791
1784
  // display-message N+1 fallback is gone). leader_env stays the reverse-env real-machine bit.
1792
1785
  #[test]
1793
1786
  fn list_targets_argv_and_parses_tmux_pane_format() {
1794
- const FMT: &str = "#{pane_id}__TA_FIELD__#{session_name}__TA_FIELD__#{window_index}__TA_FIELD__#{window_name}__TA_FIELD__#{pane_index}__TA_FIELD__#{pane_tty}__TA_FIELD__#{pane_current_command}__TA_FIELD__#{pane_active}__TA_FIELD__#{pane_current_path}__TA_FIELD__#{session_attached}__TA_FIELD__#{pane_in_mode}__TA_FIELD__#{pane_pid}";
1795
- let stdout = "%7__TA_FIELD__team-x__TA_FIELD__0__TA_FIELD__win0__TA_FIELD__0__TA_FIELD__/dev/ttys003__TA_FIELD__codex__TA_FIELD__1__TA_FIELD__/Users/me/work__TA_FIELD__1__TA_FIELD__0__TA_FIELD__41001\n\
1796
- %8__TA_FIELD__team-x__TA_FIELD__1__TA_FIELD__win1__TA_FIELD__0__TA_FIELD__/dev/ttys004__TA_FIELD__node__TA_FIELD__0__TA_FIELD__/Users/me/other__TA_FIELD__0__TA_FIELD__0__TA_FIELD__41002\n";
1787
+ const FMT: &str = "#{pane_id}__TA_FIELD__#{session_name}__TA_FIELD__#{window_index}__TA_FIELD__#{window_name}__TA_FIELD__#{pane_index}__TA_FIELD__#{pane_tty}__TA_FIELD__#{pane_current_command}__TA_FIELD__#{pane_active}__TA_FIELD__#{pane_current_path}__TA_FIELD__#{session_attached}__TA_FIELD__#{pane_in_mode}__TA_FIELD__#{pane_pid}__TA_FIELD__#{@team_agent_pane_binding_nonce}";
1788
+ let stdout = "%7__TA_FIELD__team-x__TA_FIELD__0__TA_FIELD__win0__TA_FIELD__0__TA_FIELD__/dev/ttys003__TA_FIELD__codex__TA_FIELD__1__TA_FIELD__/Users/me/work__TA_FIELD__1__TA_FIELD__0__TA_FIELD__41001__TA_FIELD__nonce-7\n\
1789
+ %8__TA_FIELD__team-x__TA_FIELD__1__TA_FIELD__win1__TA_FIELD__0__TA_FIELD__/dev/ttys004__TA_FIELD__node__TA_FIELD__0__TA_FIELD__/Users/me/other__TA_FIELD__0__TA_FIELD__0__TA_FIELD__41002__TA_FIELD__\n";
1797
1790
  let (be, rec) = backend_with(MockResp::Out(ok(stdout)), vec![]);
1798
1791
  let panes = be.list_targets().expect("list_targets ok");
1799
1792
  assert_eq!(
@@ -1845,6 +1838,19 @@ fn list_targets_argv_and_parses_tmux_pane_format() {
1845
1838
  Some(41002),
1846
1839
  "field[11] -> pane_pid (second pane)"
1847
1840
  );
1841
+ assert_eq!(
1842
+ p.leader_env
1843
+ .get(PANE_BINDING_NONCE_METADATA_KEY)
1844
+ .map(String::as_str),
1845
+ Some("nonce-7"),
1846
+ "field[12] pane option -> pane-instance binding metadata"
1847
+ );
1848
+ assert!(
1849
+ !panes[1]
1850
+ .leader_env
1851
+ .contains_key(PANE_BINDING_NONCE_METADATA_KEY),
1852
+ "an empty pane option must remain absent"
1853
+ );
1848
1854
 
1849
1855
  // nonzero exit -> empty vec (golden returncode != 0 -> []).
1850
1856
  let (be, _r) = backend_with(
@@ -1859,6 +1865,35 @@ fn list_targets_argv_and_parses_tmux_pane_format() {
1859
1865
  );
1860
1866
  }
1861
1867
 
1868
+ #[test]
1869
+ fn set_pane_binding_nonce_is_socket_scoped_and_pane_local() {
1870
+ let rec = Arc::new(Mutex::new(Vec::new()));
1871
+ let runner = MockCommandRunner {
1872
+ recorded: Arc::clone(&rec),
1873
+ stdin_recorded: Arc::new(Mutex::new(Vec::new())),
1874
+ queue: Mutex::new(VecDeque::new()),
1875
+ default: MockResp::Out(ok("")),
1876
+ };
1877
+ let be =
1878
+ TmuxBackend::with_runner_for_tmux_endpoint(Box::new(runner), "/tmp/ta-pane-binding.sock");
1879
+ be.set_pane_binding_nonce(&PaneId::new("%7"), "nonce-7")
1880
+ .expect("set pane binding nonce");
1881
+ assert_eq!(
1882
+ rec.lock().unwrap()[0],
1883
+ svec(&[
1884
+ "tmux",
1885
+ "-S",
1886
+ "/tmp/ta-pane-binding.sock",
1887
+ "set-option",
1888
+ "-p",
1889
+ "-t",
1890
+ "%7",
1891
+ "@team_agent_pane_binding_nonce",
1892
+ "nonce-7",
1893
+ ])
1894
+ );
1895
+ }
1896
+
1862
1897
  // ── 12. attach_session (TRANSPORT TRIO) — `tmux attach-session -t <s>` -> Attached ──────────────
1863
1898
  // Golden tmux attach is `tmux attach-session -t <session>`; a successful attach -> AttachOutcome::
1864
1899
  // Attached. RED today: attach_session is unimplemented!() -> PANIC. The in-process lock asserts the
@@ -40,6 +40,9 @@ use crate::transport::{
40
40
  TransportError, TurnVerification, WindowName,
41
41
  };
42
42
 
43
+ pub const PANE_BINDING_NONCE_METADATA_KEY: &str = "TEAM_AGENT_PANE_BINDING_NONCE";
44
+ const TMUX_PANE_BINDING_NONCE_OPTION: &str = "@team_agent_pane_binding_nonce";
45
+
43
46
  /// Result of running an external command — the typed output of the OS edge.
44
47
  #[derive(Debug, Clone)]
45
48
  pub struct CommandOutput {
@@ -279,6 +282,23 @@ impl TmuxBackend {
279
282
  }
280
283
  }
281
284
 
285
+ pub(crate) fn set_pane_binding_nonce(
286
+ &self,
287
+ pane: &PaneId,
288
+ nonce: &str,
289
+ ) -> Result<(), TransportError> {
290
+ let argv = [
291
+ "tmux".to_string(),
292
+ "set-option".to_string(),
293
+ "-p".to_string(),
294
+ "-t".to_string(),
295
+ pane.as_str().to_string(),
296
+ TMUX_PANE_BINDING_NONCE_OPTION.to_string(),
297
+ nonce.to_string(),
298
+ ];
299
+ self.run_ok(&argv)
300
+ }
301
+
282
302
  /// Backend with an injected runner (tests: canned/recording tmux output). Shared default socket.
283
303
  pub fn with_runner(runner: Box<dyn CommandRunner>) -> Self {
284
304
  Self {
@@ -2340,7 +2360,7 @@ impl Transport for TmuxBackend {
2340
2360
  // killing the per-pane display-message N+1 fallback.
2341
2361
  // Use a printable sentinel so the 12-field frame stays explicit in argv/log evidence;
2342
2362
  // `parse_pane_info_line` retains compatibility with legacy tab-delimited output.
2343
- const TMUX_PANE_FORMAT: &str = "#{pane_id}__TA_FIELD__#{session_name}__TA_FIELD__#{window_index}__TA_FIELD__#{window_name}__TA_FIELD__#{pane_index}__TA_FIELD__#{pane_tty}__TA_FIELD__#{pane_current_command}__TA_FIELD__#{pane_active}__TA_FIELD__#{pane_current_path}__TA_FIELD__#{session_attached}__TA_FIELD__#{pane_in_mode}__TA_FIELD__#{pane_pid}";
2363
+ const TMUX_PANE_FORMAT: &str = "#{pane_id}__TA_FIELD__#{session_name}__TA_FIELD__#{window_index}__TA_FIELD__#{window_name}__TA_FIELD__#{pane_index}__TA_FIELD__#{pane_tty}__TA_FIELD__#{pane_current_command}__TA_FIELD__#{pane_active}__TA_FIELD__#{pane_current_path}__TA_FIELD__#{session_attached}__TA_FIELD__#{pane_in_mode}__TA_FIELD__#{pane_pid}__TA_FIELD__#{@team_agent_pane_binding_nonce}";
2344
2364
  let argv = self.tmux_argv(&[
2345
2365
  "tmux".to_string(),
2346
2366
  "list-panes".to_string(),
@@ -2544,6 +2564,13 @@ fn parse_pane_info_line(line: &str) -> Option<PaneInfo> {
2544
2564
  if fields.len() < 11 {
2545
2565
  return None;
2546
2566
  }
2567
+ let mut leader_env = BTreeMap::new();
2568
+ if let Some(nonce) = fields.get(12).and_then(|raw| non_empty(raw)) {
2569
+ leader_env.insert(
2570
+ PANE_BINDING_NONCE_METADATA_KEY.to_string(),
2571
+ nonce.to_string(),
2572
+ );
2573
+ }
2547
2574
  Some(PaneInfo {
2548
2575
  pane_id: PaneId::new(fields[0]),
2549
2576
  session: SessionName::new(fields[1]),
@@ -2555,7 +2582,7 @@ fn parse_pane_info_line(line: &str) -> Option<PaneInfo> {
2555
2582
  active: fields[7] == "1",
2556
2583
  current_path: non_empty(fields[8]).map(PathBuf::from),
2557
2584
  pane_pid: fields.get(11).and_then(|raw| parse_optional_u32(raw)),
2558
- leader_env: BTreeMap::new(),
2585
+ leader_env,
2559
2586
  })
2560
2587
  }
2561
2588
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.5.51",
3
+ "version": "0.5.53",
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.51",
24
- "@team-agent/cli-darwin-x64": "0.5.51",
25
- "@team-agent/cli-linux-x64": "0.5.51"
23
+ "@team-agent/cli-darwin-arm64": "0.5.53",
24
+ "@team-agent/cli-darwin-x64": "0.5.53",
25
+ "@team-agent/cli-linux-x64": "0.5.53"
26
26
  },
27
27
  "scripts": {
28
28
  "postinstall": "node npm/bincheck.mjs",
@@ -87,15 +87,15 @@ Use `team-agent attach-leader` / `team-agent claim-leader` to bind the leader pa
87
87
 
88
88
  | Provider | Resume | Turn-state detection | Per-worker model override | Native session fork |
89
89
  |---|---|---|---|---|
90
- | `claude` / `claude_code` | yes (`--resume <id>`, transcript-verified) | yes (JSONL stream) | yes (role `model` overrides `provider_models`) | yes (`--fork-session` + new `--session-id`) |
90
+ | `claude` / `claude_code` | yes (`--resume <id>`, transcript-verified) | yes (JSONL stream) | yes (role `model` overrides `provider_models`) | yes (snapshot copy + only `--resume <snapshot-id>`) |
91
91
  | `codex` | yes (`codex resume <id>`, session-store-verified) | yes (turn JSONL) | yes (role `model`) | yes (`codex fork`) |
92
- | `copilot` | yes (`copilot --resume <id|name>`, sqlite `sessions` row) | not yet (phase 1: `provider.classify.unsupported` event) | yes (role `model`) | **no `CapabilityUnsupported`** |
92
+ | `copilot` | yes (`copilot --resume <id|name>`, sqlite `sessions` row) | not yet (phase 1: `provider.classify.unsupported` event) | yes (role `model`) | yes (isolated `COPILOT_HOME` store fork) |
93
93
  | `gemini_cli` | no | no | yes | no |
94
94
  | `fake` (testing only) | no | no | n/a | no |
95
95
 
96
96
  Notes:
97
97
  - Per-worker model override means a role-doc `model:` value wins over `TEAM.md` `provider_models.<provider>`; subscription defaults still fill blanks.
98
- - Copilot's `caps.fork=false` is a hard refusal in 0.3.7 `team-agent fork-agent` against a Copilot worker returns a structured `CapabilityUnsupported` error instead of falling back to a fresh spawn (honest by design).
98
+ - Copilot fork copies the source session into an isolated `COPILOT_HOME` and rekeys its SQLite session references atomically. Missing or incomplete backing fails closed; it never falls back to a fresh spawn.
99
99
  - Copilot phase-1 idle/turn detection is intentionally Unknown; tick emits a single explicit `provider.classify.unsupported` event per state change (P4 dedup), never a silent default.
100
100
 
101
101
  ## Provider Prep
@@ -213,10 +213,15 @@ team-agent add-agent reviewer --role-file .team/current/agents/reviewer.md --wor
213
213
  Semantic distinction:
214
214
 
215
215
  - `team-agent add-agent <agent> --role-file <file>` — add a **new** worker not yet in team state.
216
+ - `team-agent clone-agent <source> --as <new>` — reread the source worker's latest role file and start a fresh provider seat. It never copies conversation context. Success is initially honest `capture_state: pending_first_turn` with `session_id`, `new_session_id`, and `backing_path` all null; after the first turn, canonical capture changes the state to `captured` and fills the backing tuple.
217
+ - `team-agent fork-agent <source> --as <new>` — reread the same latest role file and create a distinct, verified provider session that forks the source context. If the provider backing cannot be verified, the command fails and rolls back instead of silently cloning fresh.
216
218
  - `team-agent start-agent <agent>` — (re)launch a worker that **already exists** in team state but whose window is missing.
219
+ - `team-agent reset-agent <agent> --discard-session` — keep the same seat and deliberately start it with fresh context.
217
220
  - `team-agent restart .` — resume a fully **stopped** team from stored worker sessions.
218
221
  - `team-agent quick-start <dir>` — first-time team creation from role docs; for existing teams use `restart`, and use `restart --allow-fresh` only after explicit user consent to discard context.
219
222
 
223
+ Clone/fork names are always explicit: run concurrent calls with a different `--as` value for each new seat. Fork success includes a verified new `session_id` and independent backing; a tmux window alone is not fork success. Clone success uses the honest `pending_first_turn` state above until first-turn capture, never a fabricated verified tuple. Updating the source role file affects the next clone/fork without requiring a full-team rebuild. Automatic knowledge write-back from a clone/fork into the source role file is not provided.
224
+
220
225
  Removing a worker at runtime is the symmetric `team-agent remove-agent <agent> --workspace . --confirm`.
221
226
 
222
227
  ## Worker Protocol