@team-agent/installer 0.5.37 → 0.5.39
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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/diagnose.rs +28 -3
- package/crates/team-agent/src/lifecycle/display.rs +58 -121
- package/crates/team-agent/src/lifecycle/launch.rs +44 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +18 -5
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +569 -4
- package/crates/team-agent/src/lifecycle/restart.rs +6 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +11 -1
- package/crates/team-agent/src/tmux_backend.rs +175 -0
- package/crates/team-agent/src/transport.rs +38 -0
- package/package.json +4 -4
|
@@ -50,6 +50,12 @@ pub(crate) use common::session_identity_probe_for_agent;
|
|
|
50
50
|
pub(crate) use common::lifecycle_worker_tmux_backend_for_selected_state;
|
|
51
51
|
pub use orchestrator::{halt_plan, plan_status};
|
|
52
52
|
pub(crate) use rebuild::restart_with_transport_with_session_convergence_deadline;
|
|
53
|
+
// 0.5.38 (`.team/artifacts/startup-latency-locate.md` §5): expose the phase
|
|
54
|
+
// timer + worker timing writer so `lifecycle::launch::launch_with_transport_in_workspace`
|
|
55
|
+
// can emit the same instrumentation event family with `source="launch"`.
|
|
56
|
+
pub(crate) use rebuild::{
|
|
57
|
+
provider_wire_from_state, write_worker_spawn_timing_event, RestartPhaseTimer,
|
|
58
|
+
};
|
|
53
59
|
pub use rebuild::{
|
|
54
60
|
restart, restart_candidates, restart_with_session_convergence_deadline, restart_with_transport,
|
|
55
61
|
restart_with_transport_with_readiness_deadline, select_restart_state,
|
|
@@ -336,7 +336,17 @@ if [ "$track" != 1 ]; then
|
|
|
336
336
|
fi
|
|
337
337
|
exit 127
|
|
338
338
|
fi
|
|
339
|
-
|
|
339
|
+
# 0.5.39 Slice 2: worker spawn now goes through the worker shell
|
|
340
|
+
# wrapper (tmux-server-death-locate §7 Slice 2), which embeds a
|
|
341
|
+
# printf format literal containing "\n" bytes so the pane returns to
|
|
342
|
+
# an interactive shell with an explicit exit marker instead of
|
|
343
|
+
# collapsing to `[exited]`. Those embedded newlines land inside the
|
|
344
|
+
# tmux argv payload, so a naive `printf '%s\n' "$*"` writes multiple
|
|
345
|
+
# lines per tmux invocation and downstream `raw.lines()` scans see
|
|
346
|
+
# fake "argv" lines that carry only the marker text. Escape newlines
|
|
347
|
+
# to a literal `\n` marker so one shim call = one log line = one
|
|
348
|
+
# assertion target.
|
|
349
|
+
printf '%s\n' "$*" | awk 'BEGIN{ORS=""} {print sep $0; sep="\\n"} END{print "\n"}' >> "$log"
|
|
340
350
|
case "$*" in
|
|
341
351
|
*"-S $expected"*) ;;
|
|
342
352
|
*)
|
|
@@ -1509,6 +1509,149 @@ pub fn leader_provider_exit_marker(provider_label: &str) -> String {
|
|
|
1509
1509
|
)
|
|
1510
1510
|
}
|
|
1511
1511
|
|
|
1512
|
+
/// 0.5.39 Slice 1 (tmux-server-death-locate §7 Slice 1): ambient-tmux
|
|
1513
|
+
/// leader-pane probe. Kept inside `tmux_backend` because it is
|
|
1514
|
+
/// definitionally ambient — its job is to discover *which* session/pane
|
|
1515
|
+
/// the leader process is currently inside via $TMUX/$TMUX_PANE +
|
|
1516
|
+
/// `tmux display-message`. Everywhere else in the codebase, tmux ops
|
|
1517
|
+
/// must go through a socket-scoped `TmuxBackend` (that constraint is
|
|
1518
|
+
/// enforced by `n16_tmux_socket_invariant_red.rs` +
|
|
1519
|
+
/// `tmux_server_death_0539_contract.rs::display_cleanup_...`); this
|
|
1520
|
+
/// helper is the single controlled exception.
|
|
1521
|
+
///
|
|
1522
|
+
/// Returns `(session_name, Some(pane_id))` when the ambient tmux
|
|
1523
|
+
/// responds, or `None` if `$TMUX` is unset / `display-message` fails.
|
|
1524
|
+
pub fn probe_ambient_leader_pane_info() -> Option<(String, Option<String>)> {
|
|
1525
|
+
let pane = std::env::var("TMUX_PANE")
|
|
1526
|
+
.ok()
|
|
1527
|
+
.filter(|value| !value.is_empty());
|
|
1528
|
+
let mut commands: Vec<Vec<String>> = Vec::new();
|
|
1529
|
+
if let Some(pane) = pane.as_deref() {
|
|
1530
|
+
commands.push(vec![
|
|
1531
|
+
"display-message".to_string(),
|
|
1532
|
+
"-p".to_string(),
|
|
1533
|
+
"-t".to_string(),
|
|
1534
|
+
pane.to_string(),
|
|
1535
|
+
"-F".to_string(),
|
|
1536
|
+
"#{session_name}\t#{pane_id}".to_string(),
|
|
1537
|
+
]);
|
|
1538
|
+
commands.push(vec![
|
|
1539
|
+
"display-message".to_string(),
|
|
1540
|
+
"-p".to_string(),
|
|
1541
|
+
"-t".to_string(),
|
|
1542
|
+
pane.to_string(),
|
|
1543
|
+
"-F".to_string(),
|
|
1544
|
+
"#{session_name}".to_string(),
|
|
1545
|
+
]);
|
|
1546
|
+
}
|
|
1547
|
+
if std::env::var("TMUX").is_ok_and(|value| !value.is_empty()) {
|
|
1548
|
+
commands.push(vec![
|
|
1549
|
+
"display-message".to_string(),
|
|
1550
|
+
"-p".to_string(),
|
|
1551
|
+
"-F".to_string(),
|
|
1552
|
+
"#{session_name}\t#{pane_id}".to_string(),
|
|
1553
|
+
]);
|
|
1554
|
+
commands.push(vec![
|
|
1555
|
+
"display-message".to_string(),
|
|
1556
|
+
"-p".to_string(),
|
|
1557
|
+
"-F".to_string(),
|
|
1558
|
+
"#{session_name}".to_string(),
|
|
1559
|
+
]);
|
|
1560
|
+
}
|
|
1561
|
+
for command in commands {
|
|
1562
|
+
let output = match std::process::Command::new("tmux").args(&command).output() {
|
|
1563
|
+
Ok(output) if output.status.success() => output,
|
|
1564
|
+
_ => continue,
|
|
1565
|
+
};
|
|
1566
|
+
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
1567
|
+
let Some(line) = stdout.lines().find(|line| !line.trim().is_empty()) else {
|
|
1568
|
+
continue;
|
|
1569
|
+
};
|
|
1570
|
+
let line = line.trim();
|
|
1571
|
+
let parts: Vec<&str> = line.split('\t').collect();
|
|
1572
|
+
let parsed = match parts.as_slice() {
|
|
1573
|
+
[session, pane_str, ..] if !session.is_empty() && !session.starts_with('%') => Some((
|
|
1574
|
+
(*session).to_string(),
|
|
1575
|
+
(!pane_str.is_empty()).then(|| (*pane_str).to_string()),
|
|
1576
|
+
)),
|
|
1577
|
+
[pane_str, session, ..] if pane_str.starts_with('%') && !session.is_empty() => {
|
|
1578
|
+
Some(((*session).to_string(), Some((*pane_str).to_string())))
|
|
1579
|
+
}
|
|
1580
|
+
[session] if !session.is_empty() && !session.starts_with('%') => {
|
|
1581
|
+
Some(((*session).to_string(), None))
|
|
1582
|
+
}
|
|
1583
|
+
_ => None,
|
|
1584
|
+
};
|
|
1585
|
+
if parsed.is_some() {
|
|
1586
|
+
return parsed;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
None
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
/// 0.5.39 Slice 2 (tmux-server-death-locate §11.2): single-source worker
|
|
1593
|
+
/// exit marker prefix. Same envelope shape as `LEADER_PROVIDER_EXIT_MARKER_*`
|
|
1594
|
+
/// but distinct so status/classifier code can tell "leader pane fell back
|
|
1595
|
+
/// to shell" from "worker pane fell back to shell". Format:
|
|
1596
|
+
/// `"[team-agent worker] {provider_label} exited with {rc}"`.
|
|
1597
|
+
pub const WORKER_PROVIDER_EXIT_MARKER_PREFIX: &str = "[team-agent worker]";
|
|
1598
|
+
pub const WORKER_PROVIDER_EXIT_MARKER_SUFFIX: &str = "exited with";
|
|
1599
|
+
|
|
1600
|
+
/// 0.5.39 Slice 2: build the worker exit marker text for `provider_label`.
|
|
1601
|
+
/// Used by both the worker shell wrapper (printf source) and future
|
|
1602
|
+
/// status/classifier code (capture substring) so they cannot drift.
|
|
1603
|
+
pub fn worker_provider_exit_marker(provider_label: &str) -> String {
|
|
1604
|
+
format!(
|
|
1605
|
+
"{WORKER_PROVIDER_EXIT_MARKER_PREFIX} {provider_label} {WORKER_PROVIDER_EXIT_MARKER_SUFFIX}"
|
|
1606
|
+
)
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
/// 0.5.39 Slice 2 (tmux-server-death-locate §7 Slice 2): worker shell
|
|
1610
|
+
/// wrapper. Same shape as `leader_shell_wrapper_command` — provider runs
|
|
1611
|
+
/// as a CHILD of a long-lived shell so provider exit does NOT collapse the
|
|
1612
|
+
/// worker pane (which under upstream tmux 3.6a private-server bugs can
|
|
1613
|
+
/// cascade into whole-server death). When the provider exits, the worker
|
|
1614
|
+
/// pane returns to an interactive shell with an explicit worker exit
|
|
1615
|
+
/// marker, matching manual `tmux new-window` then `<provider>` behaviour.
|
|
1616
|
+
pub fn worker_shell_wrapper_command(
|
|
1617
|
+
argv: &[String],
|
|
1618
|
+
cwd: &Path,
|
|
1619
|
+
env: &BTreeMap<String, String>,
|
|
1620
|
+
env_unset: &[String],
|
|
1621
|
+
provider_label: &str,
|
|
1622
|
+
) -> String {
|
|
1623
|
+
let unset_set: std::collections::BTreeSet<&str> =
|
|
1624
|
+
env_unset.iter().map(String::as_str).collect();
|
|
1625
|
+
let mut parts = Vec::new();
|
|
1626
|
+
parts.push("cd".to_string());
|
|
1627
|
+
parts.push(shell_quote(&cwd.to_string_lossy()));
|
|
1628
|
+
parts.push("&&".to_string());
|
|
1629
|
+
for key in env_unset {
|
|
1630
|
+
parts.push("unset".to_string());
|
|
1631
|
+
parts.push(key.clone());
|
|
1632
|
+
parts.push("&&".to_string());
|
|
1633
|
+
}
|
|
1634
|
+
for (key, value) in env {
|
|
1635
|
+
if unset_set.contains(key.as_str()) {
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
parts.push(format!("{key}={}", shell_quote(value)));
|
|
1639
|
+
}
|
|
1640
|
+
parts.extend(argv.iter().map(|arg| shell_quote(arg)));
|
|
1641
|
+
parts.push(";".to_string());
|
|
1642
|
+
parts.push("rc=$?;".to_string());
|
|
1643
|
+
parts.push("printf".to_string());
|
|
1644
|
+
parts.push(shell_quote(&format!(
|
|
1645
|
+
"\n{} %s\n",
|
|
1646
|
+
worker_provider_exit_marker(provider_label)
|
|
1647
|
+
)));
|
|
1648
|
+
parts.push("\"$rc\";".to_string());
|
|
1649
|
+
parts.push("exec".to_string());
|
|
1650
|
+
parts.push("\"${SHELL:-/bin/zsh}\"".to_string());
|
|
1651
|
+
parts.push("-l".to_string());
|
|
1652
|
+
parts.join(" ")
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1512
1655
|
/// 0.4.x (CR C-2): leader shell wrapper — provider runs as a CHILD of a
|
|
1513
1656
|
/// long-lived shell, not as the pane's primary process. When the provider
|
|
1514
1657
|
/// exits, the pane returns to an interactive shell with an explicit exit
|
|
@@ -1670,6 +1813,38 @@ impl Transport for TmuxBackend {
|
|
|
1670
1813
|
self.spawn_split(session, window, argv, cwd, env, env_unset)
|
|
1671
1814
|
}
|
|
1672
1815
|
|
|
1816
|
+
/// 0.5.39 Slice 2: TmuxBackend override of the worker-shell-wrapper
|
|
1817
|
+
/// variant. Same mechanism as the leader wrapper (child provider under
|
|
1818
|
+
/// long-lived shell), but the marker text is distinct so downstream
|
|
1819
|
+
/// classifiers can tell leader vs worker provider exit apart.
|
|
1820
|
+
fn spawn_first_with_worker_shell_wrapper(
|
|
1821
|
+
&self,
|
|
1822
|
+
session: &SessionName,
|
|
1823
|
+
window: &WindowName,
|
|
1824
|
+
argv: &[String],
|
|
1825
|
+
cwd: &Path,
|
|
1826
|
+
env: &BTreeMap<String, String>,
|
|
1827
|
+
env_unset: &[String],
|
|
1828
|
+
provider_label: &str,
|
|
1829
|
+
) -> Result<SpawnResult, TransportError> {
|
|
1830
|
+
let command = worker_shell_wrapper_command(argv, cwd, env, env_unset, provider_label);
|
|
1831
|
+
self.spawn_with_command(session, window, &command, true)
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
fn spawn_into_with_worker_shell_wrapper(
|
|
1835
|
+
&self,
|
|
1836
|
+
session: &SessionName,
|
|
1837
|
+
window: &WindowName,
|
|
1838
|
+
argv: &[String],
|
|
1839
|
+
cwd: &Path,
|
|
1840
|
+
env: &BTreeMap<String, String>,
|
|
1841
|
+
env_unset: &[String],
|
|
1842
|
+
provider_label: &str,
|
|
1843
|
+
) -> Result<SpawnResult, TransportError> {
|
|
1844
|
+
let command = worker_shell_wrapper_command(argv, cwd, env, env_unset, provider_label);
|
|
1845
|
+
self.spawn_with_command(session, window, &command, false)
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1673
1848
|
/// 0.4.x (CR C-2): TmuxBackend override of the leader-shell-wrapper
|
|
1674
1849
|
/// variant. Builds the wrapper shell line via
|
|
1675
1850
|
/// `leader_shell_wrapper_command` and runs it through
|
|
@@ -589,6 +589,44 @@ pub trait Transport: Send + Sync {
|
|
|
589
589
|
self.spawn_into(session, window, argv, cwd, env)
|
|
590
590
|
}
|
|
591
591
|
|
|
592
|
+
/// 0.5.39 Slice 2 (tmux-server-death-locate §7 Slice 2): worker spawn
|
|
593
|
+
/// variant that reuses the leader-wrapper mechanism so worker provider
|
|
594
|
+
/// exit does not collapse the pane into `[exited]` (which under
|
|
595
|
+
/// upstream tmux 3.6a private-server bugs can cascade into server
|
|
596
|
+
/// death). When the provider exits, the worker pane returns to an
|
|
597
|
+
/// interactive shell with an explicit worker exit marker — matching
|
|
598
|
+
/// manual `tmux new-window` then `<provider>` behaviour. Default falls
|
|
599
|
+
/// back to plain `spawn_first_with_env_unset` for backends that have
|
|
600
|
+
/// no shell layer (test-only `OfflineTransport`).
|
|
601
|
+
fn spawn_first_with_worker_shell_wrapper(
|
|
602
|
+
&self,
|
|
603
|
+
session: &SessionName,
|
|
604
|
+
window: &WindowName,
|
|
605
|
+
argv: &[String],
|
|
606
|
+
cwd: &Path,
|
|
607
|
+
env: &BTreeMap<String, String>,
|
|
608
|
+
env_unset: &[String],
|
|
609
|
+
provider_label: &str,
|
|
610
|
+
) -> Result<SpawnResult, TransportError> {
|
|
611
|
+
let _ = provider_label;
|
|
612
|
+
self.spawn_first_with_env_unset(session, window, argv, cwd, env, env_unset)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/// 同 [`Transport::spawn_first_with_worker_shell_wrapper`],对应 `spawn_into`。
|
|
616
|
+
fn spawn_into_with_worker_shell_wrapper(
|
|
617
|
+
&self,
|
|
618
|
+
session: &SessionName,
|
|
619
|
+
window: &WindowName,
|
|
620
|
+
argv: &[String],
|
|
621
|
+
cwd: &Path,
|
|
622
|
+
env: &BTreeMap<String, String>,
|
|
623
|
+
env_unset: &[String],
|
|
624
|
+
provider_label: &str,
|
|
625
|
+
) -> Result<SpawnResult, TransportError> {
|
|
626
|
+
let _ = provider_label;
|
|
627
|
+
self.spawn_into_with_env_unset(session, window, argv, cwd, env, env_unset)
|
|
628
|
+
}
|
|
629
|
+
|
|
592
630
|
/// 0.4.x (CR C-2): leader-specific spawn variant. Instead of `exec <cmd>`
|
|
593
631
|
/// (which makes the provider the pane's primary process and turns the
|
|
594
632
|
/// pane into `[exited]` when the provider exits), build a shell line
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.39",
|
|
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.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.5.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.5.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.5.39",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.39",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.39"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|