@team-agent/installer 0.5.105 → 0.5.106
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/README.md +5 -3
- package/README.zh.md +4 -3
- package/crates/team-agent/src/cli/adapters.rs +15 -1
- package/crates/team-agent/src/cli/emit.rs +33 -13
- package/crates/team-agent/src/cli/mod.rs +148 -21
- package/crates/team-agent/src/cli/status_port/brief.rs +62 -3
- package/crates/team-agent/src/cli/tests/run_delegation.rs +4 -2
- package/crates/team-agent/src/cli/tests/status_send.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +8 -3
- package/crates/team-agent/src/lifecycle/restart/agent.rs +44 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +24 -17
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +22 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +43 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +71 -0
- package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +6 -2
- package/crates/team-agent/src/state/projection.rs +15 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +137 -0
- package/crates/team-agent/src/tmux_backend.rs +110 -0
- package/crates/team-agent/src/transport/test_support.rs +35 -0
- package/crates/team-agent/src/transport/tests/wire.rs +32 -3
- package/crates/team-agent/src/transport.rs +26 -12
- package/package.json +4 -4
- package/skills/team-agent/references/recovery-runbook.md +2 -2
|
@@ -29,6 +29,7 @@ struct OfflineState {
|
|
|
29
29
|
target_snapshots: VecDeque<Vec<PaneInfo>>,
|
|
30
30
|
windows: Vec<WindowName>,
|
|
31
31
|
pane_presence: BTreeMap<String, bool>,
|
|
32
|
+
dead_panes: BTreeMap<String, bool>,
|
|
32
33
|
spawn_failures: BTreeMap<String, String>,
|
|
33
34
|
spawned_panes_addressable: bool,
|
|
34
35
|
liveness: BTreeMap<String, PaneLiveness>,
|
|
@@ -67,6 +68,7 @@ impl Default for OfflineState {
|
|
|
67
68
|
target_snapshots: VecDeque::new(),
|
|
68
69
|
windows: Vec::new(),
|
|
69
70
|
pane_presence: BTreeMap::new(),
|
|
71
|
+
dead_panes: BTreeMap::new(),
|
|
70
72
|
spawn_failures: BTreeMap::new(),
|
|
71
73
|
spawned_panes_addressable: true,
|
|
72
74
|
liveness: BTreeMap::new(),
|
|
@@ -144,6 +146,13 @@ impl OfflineTransport {
|
|
|
144
146
|
self
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
pub fn with_pane_dead(self, pane: impl Into<String>, dead: bool) -> Self {
|
|
150
|
+
self.with_state(|state| {
|
|
151
|
+
state.dead_panes.insert(pane.into(), dead);
|
|
152
|
+
});
|
|
153
|
+
self
|
|
154
|
+
}
|
|
155
|
+
|
|
147
156
|
pub fn with_spawn_failure(self, window: impl Into<String>, error: impl Into<String>) -> Self {
|
|
148
157
|
self.with_state(|state| {
|
|
149
158
|
state.spawn_failures.insert(window.into(), error.into());
|
|
@@ -523,6 +532,13 @@ impl Transport for OfflineTransport {
|
|
|
523
532
|
Ok(self.with_state(|state| state.pane_current_commands.get(&pane_id).cloned()))
|
|
524
533
|
}
|
|
525
534
|
|
|
535
|
+
fn pane_is_dead(&self, pane: &PaneId) -> Result<Option<bool>, TransportError> {
|
|
536
|
+
Ok(self.with_state(|state| {
|
|
537
|
+
state.calls.push("pane_is_dead");
|
|
538
|
+
state.dead_panes.get(pane.as_str()).copied()
|
|
539
|
+
}))
|
|
540
|
+
}
|
|
541
|
+
|
|
526
542
|
fn liveness(&self, pane: &PaneId) -> Result<PaneLiveness, TransportError> {
|
|
527
543
|
Ok(self.with_state(|state| {
|
|
528
544
|
state.calls.push("liveness");
|
|
@@ -582,6 +598,25 @@ impl Transport for OfflineTransport {
|
|
|
582
598
|
Ok(self.with_state(|state| state.session_owner.clone()))
|
|
583
599
|
}
|
|
584
600
|
|
|
601
|
+
fn set_session_owner_with_generation(
|
|
602
|
+
&self,
|
|
603
|
+
_session: &SessionName,
|
|
604
|
+
workspace: &Path,
|
|
605
|
+
team: &str,
|
|
606
|
+
generation: &str,
|
|
607
|
+
) -> Result<(), TransportError> {
|
|
608
|
+
self.with_state(|state| {
|
|
609
|
+
state.calls.push("set_session_owner_with_generation");
|
|
610
|
+
state.session_owner = Some(super::SessionOwner {
|
|
611
|
+
workspace: workspace.canonicalize().unwrap_or_else(|_| workspace.to_path_buf())
|
|
612
|
+
.to_string_lossy().to_string(),
|
|
613
|
+
team: team.to_string(),
|
|
614
|
+
generation: generation.to_string(),
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
Ok(())
|
|
618
|
+
}
|
|
619
|
+
|
|
585
620
|
fn list_windows(&self, _session: &SessionName) -> Result<Vec<WindowName>, TransportError> {
|
|
586
621
|
Ok(self.with_state(|state| {
|
|
587
622
|
state.calls.push("list_windows");
|
|
@@ -366,16 +366,45 @@ fn inject_empty_payload_reports_empty_text_send_keys_and_turn_not_required() {
|
|
|
366
366
|
assert_eq!(report.attempts, 1);
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
#[cfg(unix)]
|
|
370
|
+
#[test]
|
|
371
|
+
#[ignore = "requires native tmux 3.5+ and python3; owns an isolated socket/session"]
|
|
372
|
+
fn submit_key_protocol_native_tmux_is_return_in_legacy_and_extended_modes() {
|
|
373
|
+
let fixture = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
374
|
+
.join("tests/fixtures/submit_key_protocol_probe.py");
|
|
375
|
+
let output = std::process::Command::new("python3")
|
|
376
|
+
.arg(fixture)
|
|
377
|
+
.args([
|
|
378
|
+
"--submit-key",
|
|
379
|
+
crate::transport::tmux_submit_key_name(Key::Enter),
|
|
380
|
+
])
|
|
381
|
+
.output()
|
|
382
|
+
.expect("run isolated raw-PTY submit diagnostic");
|
|
383
|
+
assert!(
|
|
384
|
+
output.status.success(),
|
|
385
|
+
"production submit key is not Return: stdout={} stderr={}",
|
|
386
|
+
String::from_utf8_lossy(&output.stdout),
|
|
387
|
+
String::from_utf8_lossy(&output.stderr),
|
|
388
|
+
);
|
|
389
|
+
assert!(String::from_utf8_lossy(&output.stdout)
|
|
390
|
+
.contains("ProductionSubmitReturn=verified-three-modes"));
|
|
391
|
+
}
|
|
392
|
+
|
|
369
393
|
#[test]
|
|
370
394
|
fn empty_inject_is_single_direct_send_keys_never_buffer() {
|
|
371
395
|
// 命令构造 golden(STEP-9):空文本禁走 buffer,只发一条 send-keys 提交键
|
|
372
396
|
// (tmux_io.py:42 —— tmux 拒空 buffer 会卡 trust prompt)。
|
|
373
|
-
//
|
|
397
|
+
// Return 与 Ctrl-M 只在 legacy 模式等价;提交必须保持逻辑 Enter。
|
|
374
398
|
assert_eq!(
|
|
375
399
|
tmux_empty_inject_argv(&PaneId::new("%7"), Key::Enter),
|
|
376
|
-
vec!["tmux", "send-keys", "-t", "%7", "
|
|
400
|
+
vec!["tmux", "send-keys", "-t", "%7", "Enter"]
|
|
401
|
+
);
|
|
402
|
+
assert_eq!(
|
|
403
|
+
crate::transport::tmux_send_submit_argv(&PaneId::new("%7"), Key::Enter),
|
|
404
|
+
vec!["tmux", "send-keys", "-t", "%7", "Enter"]
|
|
377
405
|
);
|
|
378
|
-
assert_eq!(crate::transport::tmux_submit_key_name(Key::Enter), "
|
|
406
|
+
assert_eq!(crate::transport::tmux_submit_key_name(Key::Enter), "Enter");
|
|
407
|
+
assert_eq!(crate::transport::tmux_submit_key_name(Key::Down), "Down");
|
|
379
408
|
}
|
|
380
409
|
|
|
381
410
|
#[test]
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
//! - name: tmux_inject_text_argv
|
|
14
14
|
//! what: 文本注入三步 argv(装 buffer→paste→删 buffer)的唯一构造点
|
|
15
15
|
//! - name: tmux_send_submit_argv
|
|
16
|
-
//! what: 提交键 argv 的唯一构造点,Enter
|
|
16
|
+
//! what: 提交键 argv 的唯一构造点,Enter 保持逻辑 Return 而非 Ctrl-M
|
|
17
17
|
//! - name: tmux_capture_argv
|
|
18
18
|
//! what: capture-pane argv 的唯一构造点
|
|
19
19
|
//! - name: tmux_query_argv
|
|
@@ -994,6 +994,12 @@ pub trait Transport: Send + Sync {
|
|
|
994
994
|
Ok(None)
|
|
995
995
|
}
|
|
996
996
|
|
|
997
|
+
/// Native dead-pane observation. Only Some(true) proves no live pane
|
|
998
|
+
/// process; a missing/unsupported sample must remain unknown.
|
|
999
|
+
fn pane_is_dead(&self, _pane: &PaneId) -> Result<Option<bool>, TransportError> {
|
|
1000
|
+
Ok(None)
|
|
1001
|
+
}
|
|
1002
|
+
|
|
997
1003
|
/// Cheap direct pane existence check when a backend can prove it. `Ok(None)`
|
|
998
1004
|
/// preserves the existing Unknown boundary.
|
|
999
1005
|
fn has_pane(&self, pane: &PaneId) -> Result<Option<bool>, TransportError> {
|
|
@@ -1085,6 +1091,17 @@ pub trait Transport: Send + Sync {
|
|
|
1085
1091
|
|
|
1086
1092
|
// —— LIFECYCLE(SL)——
|
|
1087
1093
|
|
|
1094
|
+
/// Before stopping a seat, preserve its session if removing these targets
|
|
1095
|
+
/// would otherwise destroy it. This is not called by team shutdown/restart.
|
|
1096
|
+
/// Backends whose sessions outlive their panes need no special handling.
|
|
1097
|
+
fn preserve_session_before_stop(
|
|
1098
|
+
&self,
|
|
1099
|
+
_session: &SessionName,
|
|
1100
|
+
_targets: &[Target],
|
|
1101
|
+
) -> Result<(), TransportError> {
|
|
1102
|
+
Ok(())
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1088
1105
|
fn kill_server(&self) -> Result<(), TransportError> {
|
|
1089
1106
|
Ok(())
|
|
1090
1107
|
}
|
|
@@ -1199,21 +1216,18 @@ pub fn tmux_send_keys_argv(pane: &PaneId, keys: &[Key]) -> Vec<String> {
|
|
|
1199
1216
|
|
|
1200
1217
|
/// 投递提交键的 tmux 拼写。
|
|
1201
1218
|
///
|
|
1202
|
-
///
|
|
1203
|
-
///
|
|
1204
|
-
///
|
|
1219
|
+
/// `Enter` 必须保持逻辑 Return,不能替换成控制组合 `C-m`。
|
|
1220
|
+
/// legacy PTY 中两者都是 CR,但启用 extended keys 后 `C-m` 会成为
|
|
1221
|
+
/// CSI-u / modifyOtherKeys Ctrl-M,Pi 不把它当提交;`Enter` 仍发送 CR。
|
|
1205
1222
|
/// ---
|
|
1206
|
-
/// purpose:
|
|
1223
|
+
/// purpose: 投递提交键复用通用逻辑键翻译,避免协议相关的控制键别名
|
|
1207
1224
|
/// params:
|
|
1208
|
-
/// key:
|
|
1209
|
-
/// returns:
|
|
1225
|
+
/// key: 逻辑提交键
|
|
1226
|
+
/// returns: 与 tmux_key_name 同值(含空串情形)
|
|
1210
1227
|
/// boundary: 只管拼写,不管何时提交、提交是否成功;不发送、不验证
|
|
1211
1228
|
/// ---
|
|
1212
1229
|
pub fn tmux_submit_key_name(key: Key) -> &'static str {
|
|
1213
|
-
|
|
1214
|
-
Key::Enter => "C-m",
|
|
1215
|
-
other => tmux_key_name(other),
|
|
1216
|
-
}
|
|
1230
|
+
tmux_key_name(key)
|
|
1217
1231
|
}
|
|
1218
1232
|
|
|
1219
1233
|
/// 投递提交:`send-keys -t <pane> <submit-spelling>`。
|
|
@@ -1478,7 +1492,7 @@ pub fn tmux_inject_text_argv(
|
|
|
1478
1492
|
|
|
1479
1493
|
/// 空文本 inject:纯 `send-keys -t <target> <submit_key>`,**禁** buffer 路径
|
|
1480
1494
|
/// (tmux 拒空 buffer 会卡 trust prompt;tmux_io.py:42)。
|
|
1481
|
-
/// `Key::Enter`
|
|
1495
|
+
/// `Key::Enter` 保持逻辑 `Enter`,不会在 extended keys 下误发 Ctrl-M。
|
|
1482
1496
|
/// ---
|
|
1483
1497
|
/// purpose: 空载荷注入(唤醒/ping)的 argv:只发提交键,不碰 buffer
|
|
1484
1498
|
/// params:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.106",
|
|
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.106",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.106",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.106"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|
|
@@ -54,7 +54,7 @@ Diagnosis:
|
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
56
|
team-agent status --workspace "$WORKSPACE" --json --detail
|
|
57
|
-
team-agent
|
|
57
|
+
team-agent inbox "$AGENT_ID" --workspace "$WORKSPACE" --team "$TEAM" -n 3 --json
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
What to look for:
|
|
@@ -167,7 +167,7 @@ Validate:
|
|
|
167
167
|
|
|
168
168
|
```bash
|
|
169
169
|
team-agent status --workspace "$WORKSPACE" --json --detail
|
|
170
|
-
team-agent
|
|
170
|
+
team-agent inbox "$AGENT_ID" --workspace "$WORKSPACE" --team "$TEAM" -n 3 --json
|
|
171
171
|
```
|
|
172
172
|
|
|
173
173
|
The expected result is `status="restarted"` or an equivalent running projection without duplicate-session errors. Do not treat a partial failure on one worker as permission to reset the whole team.
|