@team-agent/installer 0.5.41 → 0.5.43
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/adapters.rs +129 -54
- package/crates/team-agent/src/cli/diagnose.rs +1 -2
- package/crates/team-agent/src/cli/emit.rs +1 -7
- package/crates/team-agent/src/cli/helpers.rs +3 -1
- package/crates/team-agent/src/cli/leader.rs +2 -1
- package/crates/team-agent/src/cli/mod.rs +27 -7
- package/crates/team-agent/src/cli/named_address.rs +14 -5
- package/crates/team-agent/src/cli/profile.rs +19 -7
- package/crates/team-agent/src/cli/send.rs +9 -3
- package/crates/team-agent/src/cli/status.rs +8 -30
- package/crates/team-agent/src/cli/status_port.rs +1341 -1272
- package/crates/team-agent/src/cli/tests/base.rs +738 -660
- package/crates/team-agent/src/cli/tests/compile.rs +45 -18
- package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
- package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
- package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
- package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
- package/crates/team-agent/src/cli/tests/mod.rs +17 -7
- package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
- package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
- package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
- package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
- package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
- package/crates/team-agent/src/codex_app_server.rs +2 -5
- package/crates/team-agent/src/compiler/tests.rs +139 -33
- package/crates/team-agent/src/compiler.rs +55 -22
- package/crates/team-agent/src/conpty/backend.rs +23 -33
- package/crates/team-agent/src/coordinator/backoff.rs +2 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
- package/crates/team-agent/src/coordinator/health.rs +46 -31
- package/crates/team-agent/src/coordinator/mod.rs +3 -3
- package/crates/team-agent/src/coordinator/orphan.rs +22 -10
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +47 -52
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
- package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
- package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
- package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
- package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
- package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
- package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
- package/crates/team-agent/src/coordinator/tick.rs +19 -1
- package/crates/team-agent/src/db/message_store.rs +138 -30
- package/crates/team-agent/src/db/migration.rs +249 -61
- package/crates/team-agent/src/db/schema.rs +303 -82
- package/crates/team-agent/src/diagnose/comms.rs +9 -2
- package/crates/team-agent/src/diagnose/mod.rs +1 -3
- package/crates/team-agent/src/diagnose/orphans.rs +79 -61
- package/crates/team-agent/src/event_log.rs +70 -16
- package/crates/team-agent/src/layout/manager.rs +15 -4
- package/crates/team-agent/src/layout/mod.rs +4 -4
- package/crates/team-agent/src/layout/overlay.rs +10 -3
- package/crates/team-agent/src/layout/placement.rs +5 -1
- package/crates/team-agent/src/layout/recovery.rs +4 -2
- package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
- package/crates/team-agent/src/layout/sessions.rs +17 -9
- package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
- package/crates/team-agent/src/layout/worker_env.rs +52 -19
- package/crates/team-agent/src/leader/helpers.rs +7 -1
- package/crates/team-agent/src/leader/lease.rs +195 -82
- package/crates/team-agent/src/leader/owner_bind.rs +55 -22
- package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
- package/crates/team-agent/src/leader/rediscover.rs +74 -25
- package/crates/team-agent/src/leader/start.rs +75 -54
- package/crates/team-agent/src/leader/takeover.rs +46 -11
- package/crates/team-agent/src/leader/tests/basics.rs +320 -167
- package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
- package/crates/team-agent/src/leader/tests/identity.rs +428 -356
- package/crates/team-agent/src/leader/tests/idle.rs +285 -254
- package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
- package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
- package/crates/team-agent/src/leader/tests/mod.rs +115 -99
- package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
- package/crates/team-agent/src/lib.rs +4 -4
- package/crates/team-agent/src/lifecycle/display.rs +7 -3
- package/crates/team-agent/src/lifecycle/launch.rs +6 -1
- package/crates/team-agent/src/lifecycle/mod.rs +9 -1
- package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
- package/crates/team-agent/src/lifecycle/tests.rs +2 -2
- package/crates/team-agent/src/main.rs +4 -4
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
- package/crates/team-agent/src/mcp_server/mod.rs +11 -2
- package/crates/team-agent/src/mcp_server/types.rs +14 -3
- package/crates/team-agent/src/mcp_server/wire.rs +230 -68
- package/crates/team-agent/src/messaging/delivery.rs +20 -18
- package/crates/team-agent/src/messaging/helpers.rs +25 -4
- package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
- package/crates/team-agent/src/messaging/mod.rs +2 -3
- package/crates/team-agent/src/messaging/selftest.rs +46 -26
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
- package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
- package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
- package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
- package/crates/team-agent/src/messaging/trust.rs +25 -2
- package/crates/team-agent/src/messaging/watchers.rs +37 -9
- package/crates/team-agent/src/model/enums.rs +65 -16
- package/crates/team-agent/src/model/ids.rs +12 -3
- package/crates/team-agent/src/model/paths.rs +28 -7
- package/crates/team-agent/src/model/permissions.rs +176 -33
- package/crates/team-agent/src/model/routing.rs +66 -20
- package/crates/team-agent/src/model/spec.rs +365 -69
- package/crates/team-agent/src/model/task_graph.rs +36 -9
- package/crates/team-agent/src/model/yaml/tests.rs +24 -6
- package/crates/team-agent/src/model/yaml.rs +7 -6
- package/crates/team-agent/src/packaging/install.rs +23 -9
- package/crates/team-agent/src/packaging/migrate.rs +5 -7
- package/crates/team-agent/src/packaging/mod.rs +9 -1
- package/crates/team-agent/src/packaging/repair.rs +13 -6
- package/crates/team-agent/src/packaging/tests.rs +63 -16
- package/crates/team-agent/src/packaging/types.rs +22 -7
- package/crates/team-agent/src/platform/argv.rs +4 -1
- package/crates/team-agent/src/platform/file_lock.rs +22 -8
- package/crates/team-agent/src/platform/process.rs +21 -22
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
- package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
- package/crates/team-agent/src/provider/classify.rs +127 -42
- package/crates/team-agent/src/provider/faults.rs +17 -5
- package/crates/team-agent/src/provider/helpers.rs +6 -5
- package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
- package/crates/team-agent/src/state/repository.rs +27 -14
- package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
- package/crates/team-agent/src/tmux_backend.rs +80 -44
- package/crates/team-agent/src/topology.rs +27 -20
- package/crates/team-agent/src/transport/test_support.rs +20 -23
- package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
- package/crates/team-agent/src/transport/tests/mod.rs +178 -187
- package/crates/team-agent/src/transport/tests/wire.rs +561 -525
- package/crates/team-agent/src/transport.rs +12 -17
- package/crates/team-agent/src/transport_factory.rs +29 -14
- package/package.json +4 -4
|
@@ -1,327 +1,326 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// GROUP G — PaneField 单字段查询映射(display-message -p -F → list json 字段)
|
|
3
|
+
// 非 tmux 后端无对应概念的字段返回 Ok(None)(typed「不适用」)。
|
|
4
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
5
|
+
use super::*;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
7
|
+
#[test]
|
|
8
|
+
fn pane_field_variants_distinct() {
|
|
9
|
+
// 6 个查询字段互不相等(避免散字符串打地鼠)。
|
|
10
|
+
let all = [
|
|
11
|
+
PaneField::PaneId,
|
|
12
|
+
PaneField::PaneMode,
|
|
13
|
+
PaneField::PaneWidth,
|
|
14
|
+
PaneField::PaneCurrentCommand,
|
|
15
|
+
PaneField::PaneCurrentPath,
|
|
16
|
+
PaneField::SessionName,
|
|
17
|
+
];
|
|
18
|
+
for (i, a) in all.iter().enumerate() {
|
|
19
|
+
for (j, b) in all.iter().enumerate() {
|
|
20
|
+
assert_eq!(i == j, a == b, "{a:?} vs {b:?}");
|
|
22
21
|
}
|
|
23
22
|
}
|
|
23
|
+
}
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
#[test]
|
|
26
|
+
fn query_pane_width_returns_numeric_string() {
|
|
27
|
+
// contracts-rust-native test_query_pane_width_parity:
|
|
28
|
+
// tmux display-message '#{pane_width}' / wezterm list json size.cols。RED via stub。
|
|
29
|
+
let t = tmux();
|
|
30
|
+
let w = t
|
|
31
|
+
.query(&Target::Pane(PaneId::new("%7")), PaneField::PaneWidth)
|
|
32
|
+
.expect("query ok");
|
|
33
|
+
assert!(w.is_some(), "tmux must report a pane width");
|
|
34
|
+
assert!(
|
|
35
|
+
w.unwrap().chars().all(|c| c.is_ascii_digit()),
|
|
36
|
+
"width is numeric"
|
|
37
|
+
);
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
39
|
+
// 命令构造 golden(STEP-9):PaneWidth → display-message -p -t %7 -F '#{pane_width}'
|
|
40
|
+
// (delivery.py:34)。格式 check 不锁字段映射 —— 钉死 argv。
|
|
41
|
+
assert_eq!(
|
|
42
|
+
tmux_query_argv(&PaneId::new("%7"), PaneField::PaneWidth),
|
|
43
|
+
vec![
|
|
44
|
+
"tmux",
|
|
45
|
+
"display-message",
|
|
46
|
+
"-p",
|
|
47
|
+
"-t",
|
|
48
|
+
"%7",
|
|
49
|
+
"-F",
|
|
50
|
+
"#{pane_width}"
|
|
51
|
+
]
|
|
52
|
+
);
|
|
53
|
+
// PaneMode 用裸格式参数(无 -F),pane_mode 直传(tmux_io.py:403)。
|
|
54
|
+
assert_eq!(
|
|
55
|
+
tmux_query_argv(&PaneId::new("%7"), PaneField::PaneMode),
|
|
56
|
+
vec!["tmux", "display-message", "-p", "-t", "%7", "#{pane_mode}"]
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
60
|
+
#[test]
|
|
61
|
+
fn query_pane_mode_is_not_applicable_on_wezterm() {
|
|
62
|
+
// 设计 §3.1 query(pane_mode):非 tmux 后端无 pane-mode 概念 → recognizer 恒当 Input。
|
|
63
|
+
// typed「不适用」收口为 Ok(None)(porter 不能返回 Ok(Some("anything")) 假绿)。RED via stub。
|
|
64
|
+
let w = wezterm();
|
|
65
|
+
let mode = w
|
|
66
|
+
.query(&Target::Pane(PaneId::new("1")), PaneField::PaneMode)
|
|
67
|
+
.expect("query pane_mode ok on wezterm (no error)");
|
|
68
|
+
assert_eq!(
|
|
69
|
+
mode, None,
|
|
70
|
+
"wezterm has no pane-mode concept -> typed not-applicable is Ok(None)"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
75
|
+
// GROUP H — liveness 三态(unknown ≠ dead ≠ live)
|
|
76
|
+
// contracts-rust-native test_pane_liveness_three_valued_parity。
|
|
77
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
79
|
+
#[test]
|
|
80
|
+
fn freshly_spawned_pane_is_live_not_unknown_via_transport() {
|
|
81
|
+
// 已知存活 fixture:刚 spawn 的 pane 必须 == Live(不是 all-variant 析取的同义反复)。
|
|
82
|
+
// 镜像 conpty_cross_process...==Unknown 的强度,但锁的是 Live 这一极。
|
|
83
|
+
// RED via stub:spawn_first/liveness 都 unimplemented!()。
|
|
84
|
+
let t = tmux();
|
|
85
|
+
let env = BTreeMap::new();
|
|
86
|
+
let spawned = t
|
|
87
|
+
.spawn_first(
|
|
88
|
+
&SessionName::new("team-sess"),
|
|
89
|
+
&WindowName::new("win-1"),
|
|
90
|
+
&["sh".into(), "-lc".into(), "sleep 60".into()],
|
|
91
|
+
Path::new("/tmp/ws"),
|
|
92
|
+
&env,
|
|
93
|
+
)
|
|
94
|
+
.expect("spawn_first ok");
|
|
95
|
+
assert_eq!(
|
|
96
|
+
t.liveness(&spawned.pane_id).expect("liveness ok"),
|
|
97
|
+
PaneLiveness::Live,
|
|
98
|
+
"a freshly-spawned (sleeping) pane must read Live, never Unknown/Dead"
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
102
|
+
#[test]
|
|
103
|
+
fn conpty_cross_process_pane_is_unknown_never_optimistic_live() {
|
|
104
|
+
// contracts-rust-native:ConPTY 对非自有/跨进程 pane 判 Unknown,绝不乐观当 Live
|
|
105
|
+
// (bug-085 穷尽三态;Unknown 显式 block ping,不 fallthrough idle)。RED via stub。
|
|
106
|
+
let c = conpty();
|
|
107
|
+
let res = c
|
|
108
|
+
.liveness(&PaneId::new("foreign-leader"))
|
|
109
|
+
.expect("liveness ok");
|
|
110
|
+
assert_eq!(
|
|
111
|
+
res,
|
|
112
|
+
PaneLiveness::Unknown,
|
|
113
|
+
"ConPTY cross-process pane must be Unknown, never optimistic Live"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
118
|
+
// GROUP I — 能力性拒绝是 typed outcome,不是 Err(§10)
|
|
119
|
+
// SetEnvOutcome / AttachOutcome 的 per-backend 语义(transport-backend-design §4c/§1.3)。
|
|
120
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
119
121
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
122
|
+
#[test]
|
|
123
|
+
fn set_env_outcome_variants_carry_per_backend_semantics() {
|
|
124
|
+
// 三态语义不可混:tmux=Applied;wezterm/conpty worker=InternalizedAtSpawn;
|
|
125
|
+
// 外部 leader pane=UnsupportedForExternalPane{reason}(typed,审计,非 Err)。
|
|
126
|
+
assert_ne!(SetEnvOutcome::Applied, SetEnvOutcome::InternalizedAtSpawn);
|
|
127
|
+
let unsupported = SetEnvOutcome::UnsupportedForExternalPane {
|
|
128
|
+
reason: "external leader pane cannot be re-seeded".into(),
|
|
129
|
+
};
|
|
130
|
+
assert_ne!(unsupported, SetEnvOutcome::Applied);
|
|
131
|
+
if let SetEnvOutcome::UnsupportedForExternalPane { reason } = unsupported {
|
|
132
|
+
assert!(!reason.is_empty(), "refusal must carry an auditable reason");
|
|
133
|
+
} else {
|
|
134
|
+
panic!("expected UnsupportedForExternalPane");
|
|
134
135
|
}
|
|
136
|
+
}
|
|
135
137
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
#[test]
|
|
139
|
+
fn set_session_env_on_tmux_worker_is_applied_typed_not_err() {
|
|
140
|
+
// tmux set-environment → Applied(§4c)。能力性结果走 Ok(outcome),不走 Err。RED via stub。
|
|
141
|
+
let t = tmux();
|
|
142
|
+
let outcome = t
|
|
143
|
+
.set_session_env(&SessionName::new("team-sess"), "TEAM_AGENT_ID", "w1")
|
|
144
|
+
.expect("set_session_env must be Ok(outcome), capability refusal is NOT Err");
|
|
145
|
+
assert_eq!(outcome, SetEnvOutcome::Applied);
|
|
146
|
+
}
|
|
145
147
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
#[test]
|
|
149
|
+
fn set_session_env_on_wezterm_worker_is_internalized_at_spawn() {
|
|
150
|
+
// wezterm/conpty worker env 已在 spawn 注入 → InternalizedAtSpawn,非 Err、非假绿。RED via stub。
|
|
151
|
+
let w = wezterm();
|
|
152
|
+
let outcome = w
|
|
153
|
+
.set_session_env(&SessionName::new("team-sess"), "K", "V")
|
|
154
|
+
.expect("must be Ok(outcome)");
|
|
155
|
+
assert_eq!(outcome, SetEnvOutcome::InternalizedAtSpawn);
|
|
156
|
+
}
|
|
155
157
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
158
|
+
#[test]
|
|
159
|
+
fn attach_outcome_variants_are_distinct_per_backend() {
|
|
160
|
+
// tmux=Attached;wezterm=GuiAttachIsImplicit;conpty=Unsupported{reason}。
|
|
161
|
+
assert_ne!(AttachOutcome::Attached, AttachOutcome::GuiAttachIsImplicit);
|
|
162
|
+
let unsup = AttachOutcome::Unsupported {
|
|
163
|
+
reason: "conpty has no attach concept".into(),
|
|
164
|
+
};
|
|
165
|
+
assert_ne!(unsup, AttachOutcome::Attached);
|
|
166
|
+
if let AttachOutcome::Unsupported { reason } = unsup {
|
|
167
|
+
assert!(!reason.is_empty());
|
|
168
|
+
} else {
|
|
169
|
+
panic!("expected Unsupported");
|
|
169
170
|
}
|
|
171
|
+
}
|
|
170
172
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
other => panic!("expected AttachOutcome::Unsupported, got {other:?}"),
|
|
173
|
+
#[test]
|
|
174
|
+
fn attach_session_conpty_is_unsupported_typed_not_err() {
|
|
175
|
+
// contracts/design §3.1:conpty attach_session → Unsupported(typed,不假绿,非 Err)。RED via stub。
|
|
176
|
+
let c = conpty();
|
|
177
|
+
let outcome = c
|
|
178
|
+
.attach_session(&SessionName::new("team-sess"))
|
|
179
|
+
.expect("capability refusal must be Ok(Unsupported), not Err");
|
|
180
|
+
// §10 不变量:能力性拒绝必须携带可审计 reason(不仅锁 variant,还锁 payload 非空)。
|
|
181
|
+
match outcome {
|
|
182
|
+
AttachOutcome::Unsupported { reason } => {
|
|
183
|
+
assert!(
|
|
184
|
+
!reason.is_empty(),
|
|
185
|
+
"ConPTY attach refusal must carry an auditable reason"
|
|
186
|
+
);
|
|
187
187
|
}
|
|
188
|
+
other => panic!("expected AttachOutcome::Unsupported, got {other:?}"),
|
|
188
189
|
}
|
|
190
|
+
}
|
|
189
191
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
192
|
+
#[test]
|
|
193
|
+
fn attach_session_wezterm_gui_attach_is_implicit() {
|
|
194
|
+
// wezterm GUI 启动即 attach,无独立动作 → GuiAttachIsImplicit。RED via stub。
|
|
195
|
+
let w = wezterm();
|
|
196
|
+
let outcome = w
|
|
197
|
+
.attach_session(&SessionName::new("team-sess"))
|
|
198
|
+
.expect("must be Ok(outcome)");
|
|
199
|
+
assert_eq!(outcome, AttachOutcome::GuiAttachIsImplicit);
|
|
200
|
+
}
|
|
199
201
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
203
|
+
// GROUP J — TransportError Display(thiserror;lib 边界)+ I/O 才走 Err
|
|
204
|
+
// bug-084:os I/O/timeout 必须 Result 化,绝不裸穿 coordinator tick。
|
|
205
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
204
206
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
e.to_string(),
|
|
214
|
-
"inject failed at stage PasteBuffer: boom"
|
|
215
|
-
);
|
|
207
|
+
#[test]
|
|
208
|
+
fn transport_error_display_messages_byte_locked() {
|
|
209
|
+
// 错误信息含定位字段(backend/stage/argv/target),便于 §5 系统集成调试。
|
|
210
|
+
let e = TransportError::Inject {
|
|
211
|
+
stage: InjectStage::PasteBuffer,
|
|
212
|
+
source: std::io::Error::other("boom"),
|
|
213
|
+
};
|
|
214
|
+
assert_eq!(e.to_string(), "inject failed at stage PasteBuffer: boom");
|
|
216
215
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
216
|
+
let sub = TransportError::Subprocess {
|
|
217
|
+
argv: vec!["tmux".into(), "paste-buffer".into()],
|
|
218
|
+
code: Some(1),
|
|
219
|
+
stderr: "no such pane".into(),
|
|
220
|
+
};
|
|
221
|
+
assert_eq!(
|
|
222
|
+
sub.to_string(),
|
|
223
|
+
"subprocess [\"tmux\", \"paste-buffer\"] exited with Some(1): no such pane"
|
|
224
|
+
);
|
|
226
225
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
226
|
+
let mux = TransportError::MuxUnavailable {
|
|
227
|
+
backend: BackendKind::WezTerm,
|
|
228
|
+
detail: "mux server not reachable".into(),
|
|
229
|
+
};
|
|
230
|
+
assert_eq!(
|
|
231
|
+
mux.to_string(),
|
|
232
|
+
"mux unavailable on WezTerm: mux server not reachable"
|
|
233
|
+
);
|
|
235
234
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
235
|
+
let nf = TransportError::TargetNotFound {
|
|
236
|
+
target: "%99".into(),
|
|
237
|
+
};
|
|
238
|
+
assert_eq!(nf.to_string(), "target not found: %99");
|
|
239
|
+
}
|
|
241
240
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
241
|
+
#[test]
|
|
242
|
+
fn io_error_converts_into_transport_error_via_from() {
|
|
243
|
+
// bug-084:I/O 错误 typed 化(#[from] std::io::Error)。
|
|
244
|
+
let io = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
|
|
245
|
+
let te: TransportError = io.into();
|
|
246
|
+
assert!(matches!(te, TransportError::Io(_)));
|
|
247
|
+
}
|
|
249
248
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
249
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
250
|
+
// GROUP K — backend kind 诊断标识 + buffer 不进 trait(内化)
|
|
251
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
253
252
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
253
|
+
#[test]
|
|
254
|
+
fn backend_kind_is_reported_per_backend() {
|
|
255
|
+
assert_eq!(tmux().kind(), BackendKind::Tmux);
|
|
256
|
+
assert_eq!(wezterm().kind(), BackendKind::WezTerm);
|
|
257
|
+
assert_eq!(conpty().kind(), BackendKind::ConPty);
|
|
258
|
+
}
|
|
260
259
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
260
|
+
#[test]
|
|
261
|
+
fn buffer_lifecycle_internalized_trait_exposes_only_inject() {
|
|
262
|
+
// contracts-rust-native test_buffer_lifecycle_internalized_parity:
|
|
263
|
+
// set/load/delete-buffer 是 tmux inject 的内部细节,trait 只暴露 inject;
|
|
264
|
+
// 外部行为(注入成功、无陈旧 buffer 误注入)跨后端等价。
|
|
265
|
+
// 这里以契约形式锁住:Transport trait 上**没有**任何 buffer 方法面 —— 唯一物理出口是 inject。
|
|
266
|
+
// (编译期:若 porter 误把 buffer 提进 trait,本 stub 不再实现 trait → 编译失败。)
|
|
267
|
+
let t = tmux();
|
|
268
|
+
let report = t
|
|
269
|
+
.inject(
|
|
270
|
+
&Target::Pane(PaneId::new("%7")),
|
|
271
|
+
&InjectPayload::Text("payload".into()),
|
|
272
|
+
Key::Enter,
|
|
273
|
+
true,
|
|
274
|
+
)
|
|
275
|
+
.expect("inject ok");
|
|
276
|
+
// 非空文本注入抵达 submit,token 缺失时 verification=no_token(tmux_io.py:140)。
|
|
277
|
+
assert_eq!(report.stage_reached, InjectStage::Submit);
|
|
278
|
+
assert_eq!(report.inject_verification, InjectVerification::NoToken);
|
|
279
|
+
// 「无陈旧 buffer 误注入」是行为锁:内部 argv 序列必须**以 delete-buffer 收尾**
|
|
280
|
+
// (set-buffer → paste-buffer -p → delete-buffer;tmux_io.py:119-120)。
|
|
281
|
+
let argv = tmux_inject_text_argv(&PaneId::new("%7"), "team-agent-buf", "payload", true);
|
|
282
|
+
assert_eq!(
|
|
283
|
+
argv.last(),
|
|
284
|
+
Some(&vec![
|
|
285
|
+
"tmux".to_string(),
|
|
286
|
+
"delete-buffer".to_string(),
|
|
287
|
+
"-b".to_string(),
|
|
288
|
+
"team-agent-buf".to_string(),
|
|
289
|
+
]),
|
|
290
|
+
"inject must delete its buffer (no stale buffer mis-injection)"
|
|
291
|
+
);
|
|
292
|
+
}
|
|
294
293
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
294
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
295
|
+
// GROUP L — [真机] gated:WezTerm cli list / 正向身份登记(无法在此完整断言)
|
|
296
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
298
297
|
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
#[test]
|
|
299
|
+
#[ignore = "real-machine-gated (fixture_kind=real): needs wezterm cli list --format json; \
|
|
301
300
|
asserts pane_pid/current_command/leader_env forward-registration (GAP-2/GAP-3)"]
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
301
|
+
fn wezterm_list_targets_forward_registration_real_machine() {
|
|
302
|
+
// contracts-rust-native test_list_targets_enumerate_parity /
|
|
303
|
+
// test_worker_identity_forward_registration_parity:
|
|
304
|
+
// WezTerm 身份靠正向登记表投影(不反向读进程 env)。仅锁形状,实现期真机校验。
|
|
305
|
+
let w = wezterm();
|
|
306
|
+
let listed = w.list_targets().expect("list_targets ok");
|
|
307
|
+
for info in &listed {
|
|
308
|
+
// 正向登记:leader_env 是登记表投影,不是反向读 /proc。
|
|
309
|
+
// [真机] list json 未必给 pane_pid(GAP-2)/ current_command(GAP-3) → Option。
|
|
310
|
+
let _ = (&info.leader_env, &info.pane_pid, &info.current_command);
|
|
313
311
|
}
|
|
312
|
+
}
|
|
314
313
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
314
|
+
#[test]
|
|
315
|
+
#[ignore = "real-machine-gated (B1): kill_window/kill_session liveness transition needs a live mux"]
|
|
316
|
+
fn kill_window_then_target_dead_real_machine() {
|
|
317
|
+
// contracts-rust-native test_kill_window_stops_agent_parity:
|
|
318
|
+
// kill_window 后 liveness=Dead 且 list_targets 不再列出。实现期真机校验。
|
|
319
|
+
let t = tmux();
|
|
320
|
+
t.kill_window(&Target::Pane(PaneId::new("%7")))
|
|
321
|
+
.expect("kill_window ok");
|
|
322
|
+
assert_eq!(
|
|
323
|
+
t.liveness(&PaneId::new("%7")).expect("liveness ok"),
|
|
324
|
+
PaneLiveness::Dead
|
|
325
|
+
);
|
|
326
|
+
}
|