@team-agent/installer 0.5.74 → 0.5.77
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 +9 -0
- package/README.zh.md +8 -0
- package/crates/team-agent/src/cli/adapters.rs +23 -96
- package/crates/team-agent/src/cli/emit.rs +35 -7
- package/crates/team-agent/src/cli/mod.rs +63 -42
- package/crates/team-agent/src/cli/models.rs +138 -27
- package/crates/team-agent/src/cli/status_port/brief.rs +1137 -0
- package/crates/team-agent/src/cli/status_port.rs +8 -0
- package/crates/team-agent/src/cli/tests/mod.rs +1 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +29 -24
- package/crates/team-agent/src/cli/tests/status_brief.rs +691 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +8 -2
- package/crates/team-agent/src/layout/worker_env.rs +366 -0
- package/crates/team-agent/src/leader/lease.rs +274 -40
- package/crates/team-agent/src/leader/registry.rs +177 -19
- package/crates/team-agent/src/leader/types.rs +4 -0
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +93 -5
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +1820 -60
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +4 -0
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +138 -13
- package/crates/team-agent/src/lifecycle/restart/common.rs +3 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +11 -4
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +45 -14
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +1 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +24 -28
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +22 -10
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +110 -31
- package/crates/team-agent/src/lifecycle/types.rs +4 -0
- package/crates/team-agent/src/messaging/delivery.rs +200 -29
- package/crates/team-agent/src/messaging/leader_channel.rs +12 -3
- package/crates/team-agent/src/messaging/watchers.rs +48 -4
- package/crates/team-agent/src/state/paths.rs +17 -2
- package/crates/team-agent/src/state/persist.rs +166 -6
- package/crates/team-agent/src/state/projection.rs +18 -2
- package/crates/team-agent/src/state/repository.rs +36 -0
- package/crates/team-agent/src/state/selector.rs +23 -3
- package/crates/team-agent/src/test_support/models_timeout_fixture.rs +122 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +350 -7
- package/crates/team-agent/src/tmux_backend.rs +217 -26
- package/crates/team-agent/src/transport/test_support.rs +18 -3
- package/crates/team-agent/src/transport.rs +99 -0
- package/package.json +4 -4
|
@@ -16,15 +16,22 @@ use std::time::{Duration, Instant};
|
|
|
16
16
|
|
|
17
17
|
use super::{
|
|
18
18
|
current_paste_to_submit_floor, observe_turn_from_capture,
|
|
19
|
-
sleep_remaining_paste_to_submit_floor,
|
|
20
|
-
|
|
19
|
+
sleep_remaining_paste_to_submit_floor, worker_shell_wrapper_command,
|
|
20
|
+
with_cursor_single_enter, with_paste_to_submit_floor, CommandOutput, CommandRunner,
|
|
21
|
+
RealCommandRunner, TmuxBackend, PANE_BINDING_NONCE_METADATA_KEY,
|
|
21
22
|
};
|
|
22
|
-
use crate::
|
|
23
|
+
use crate::layout::worker_env::{
|
|
24
|
+
caller_provider_resolution_from_values, inject_current_caller_provider,
|
|
25
|
+
isolate_worker_spawn_env, worker_spawn_env, CallerProviderResolution, CALLER_ENDPOINT_ENV,
|
|
26
|
+
CALLER_PANE_ENV, CALLER_PROVIDER_ENV,
|
|
27
|
+
};
|
|
28
|
+
use crate::model::enums::{PaneLiveness, Provider};
|
|
23
29
|
use crate::transport::{
|
|
24
|
-
normalize_capture, tmux_capture_argv, tmux_query_argv, tmux_send_keys_argv,
|
|
25
|
-
tmux_submit_key_name, AttachOutcome, CaptureRange,
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
command_basename, normalize_capture, tmux_capture_argv, tmux_query_argv, tmux_send_keys_argv,
|
|
31
|
+
tmux_spawn_argv, tmux_submit_key_name, AttachOutcome, CaptureRange, CaptureSampleOutcome,
|
|
32
|
+
InjectPayload, InjectStage, InjectVerification, InputSurfaceProbe, Key, PaneField, PaneId,
|
|
33
|
+
QueryOutcome, SessionName, SetEnvOutcome, SubmitVerification, Target, Transport,
|
|
34
|
+
TransportError, TurnVerification, WindowName,
|
|
28
35
|
};
|
|
29
36
|
|
|
30
37
|
type RecordedArgv = Arc<Mutex<Vec<Vec<String>>>>;
|
|
@@ -117,6 +124,223 @@ fn fail(code: i32, stderr: &str) -> CommandOutput {
|
|
|
117
124
|
}
|
|
118
125
|
}
|
|
119
126
|
|
|
127
|
+
fn spawned_caller_env() -> (BTreeMap<String, String>, Vec<String>) {
|
|
128
|
+
let mut env = worker_spawn_env(
|
|
129
|
+
vec![
|
|
130
|
+
(CALLER_PROVIDER_ENV.to_string(), "claude".to_string()),
|
|
131
|
+
(CALLER_PANE_ENV.to_string(), "%old".to_string()),
|
|
132
|
+
(CALLER_ENDPOINT_ENV.to_string(), "/tmp/old.sock".to_string()),
|
|
133
|
+
(
|
|
134
|
+
"TEAM_AGENT_LEADER_PROVIDER".to_string(),
|
|
135
|
+
"claude".to_string(),
|
|
136
|
+
),
|
|
137
|
+
("TMUX".to_string(), "/tmp/parent.sock,1,0".to_string()),
|
|
138
|
+
("TMUX_PANE".to_string(), "%parent".to_string()),
|
|
139
|
+
],
|
|
140
|
+
Path::new("/tmp/worker"),
|
|
141
|
+
"child",
|
|
142
|
+
Some("team"),
|
|
143
|
+
None,
|
|
144
|
+
);
|
|
145
|
+
assert!(
|
|
146
|
+
!env.keys().any(|key| key.starts_with("TEAM_AGENT_CALLER_")),
|
|
147
|
+
"inherited caller namespace must be stripped before inject: {env:?}"
|
|
148
|
+
);
|
|
149
|
+
assert!(!env.contains_key("TEAM_AGENT_LEADER_PROVIDER"));
|
|
150
|
+
let env_unset = isolate_worker_spawn_env(Provider::Pi, &mut env, Vec::<String>::new());
|
|
151
|
+
inject_current_caller_provider(&mut env, Provider::Pi);
|
|
152
|
+
(env, env_unset)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fn assert_caller_unset_and_assignment_once(command: &str) {
|
|
156
|
+
assert_eq!(command.matches("unset TEAM_AGENT_CALLER_PROVIDER").count(), 1);
|
|
157
|
+
assert_eq!(command.matches("unset TEAM_AGENT_CALLER_PANE_ID").count(), 1);
|
|
158
|
+
assert_eq!(
|
|
159
|
+
command
|
|
160
|
+
.matches("unset TEAM_AGENT_CALLER_TMUX_ENDPOINT")
|
|
161
|
+
.count(),
|
|
162
|
+
1
|
|
163
|
+
);
|
|
164
|
+
assert_eq!(command.matches("TEAM_AGENT_CALLER_PROVIDER=pi").count(), 1);
|
|
165
|
+
assert_eq!(command.matches("TEAM_AGENT_CALLER_PANE_ID=").count(), 1);
|
|
166
|
+
assert_eq!(
|
|
167
|
+
command.matches("TEAM_AGENT_CALLER_TMUX_ENDPOINT=").count(),
|
|
168
|
+
1
|
|
169
|
+
);
|
|
170
|
+
assert!(command.contains("TEAM_AGENT_CALLER_PANE_ID=\"${TMUX_PANE-}\""));
|
|
171
|
+
assert!(command.contains("TEAM_AGENT_CALLER_TMUX_ENDPOINT=\"${TMUX%%,*}\""));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fn assignment_token<'a>(command: &'a str, key: &str) -> &'a str {
|
|
175
|
+
let prefix = format!("{key}=");
|
|
176
|
+
command
|
|
177
|
+
.split_whitespace()
|
|
178
|
+
.find(|token| token.starts_with(&prefix))
|
|
179
|
+
.unwrap_or_else(|| panic!("missing {key} assignment in {command}"))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
fn expand_caller_allowlist(command: &str, tmux: &str, pane: &str) -> (String, String, String) {
|
|
183
|
+
let script = format!(
|
|
184
|
+
"{}; {}; {}; printf '%s\\n%s\\n%s\\n' \"${{{}}}\" \"${{{}}}\" \"${{{}}}\"",
|
|
185
|
+
assignment_token(command, CALLER_PROVIDER_ENV),
|
|
186
|
+
assignment_token(command, CALLER_PANE_ENV),
|
|
187
|
+
assignment_token(command, CALLER_ENDPOINT_ENV),
|
|
188
|
+
CALLER_PROVIDER_ENV,
|
|
189
|
+
CALLER_PANE_ENV,
|
|
190
|
+
CALLER_ENDPOINT_ENV,
|
|
191
|
+
);
|
|
192
|
+
let output = std::process::Command::new("sh")
|
|
193
|
+
.arg("-c")
|
|
194
|
+
.arg(&script)
|
|
195
|
+
.env("TMUX", tmux)
|
|
196
|
+
.env("TMUX_PANE", pane)
|
|
197
|
+
.env_remove(CALLER_PROVIDER_ENV)
|
|
198
|
+
.env_remove(CALLER_PANE_ENV)
|
|
199
|
+
.env_remove(CALLER_ENDPOINT_ENV)
|
|
200
|
+
.output()
|
|
201
|
+
.expect("expand caller allowlist through sh");
|
|
202
|
+
assert!(
|
|
203
|
+
output.status.success(),
|
|
204
|
+
"sh expand failed: status={:?} stderr={} script={script}",
|
|
205
|
+
output.status.code(),
|
|
206
|
+
String::from_utf8_lossy(&output.stderr)
|
|
207
|
+
);
|
|
208
|
+
let stdout = String::from_utf8(output.stdout).expect("caller allowlist stdout utf8");
|
|
209
|
+
let mut lines = stdout.lines();
|
|
210
|
+
let provider = lines.next().unwrap_or("").to_string();
|
|
211
|
+
let pane = lines.next().unwrap_or("").to_string();
|
|
212
|
+
let endpoint = lines.next().unwrap_or("").to_string();
|
|
213
|
+
(provider, pane, endpoint)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
fn resolve_expanded_caller(
|
|
217
|
+
command: &str,
|
|
218
|
+
tmux: &str,
|
|
219
|
+
pane: &str,
|
|
220
|
+
scoped: Option<&str>,
|
|
221
|
+
) -> CallerProviderResolution {
|
|
222
|
+
let (provider, context_pane, context_endpoint) = expand_caller_allowlist(command, tmux, pane);
|
|
223
|
+
let current_endpoint = tmux.split(',').next().unwrap_or(tmux);
|
|
224
|
+
caller_provider_resolution_from_values(
|
|
225
|
+
Some(provider.as_str()),
|
|
226
|
+
Some(context_pane.as_str()),
|
|
227
|
+
Some(context_endpoint.as_str()),
|
|
228
|
+
None,
|
|
229
|
+
None,
|
|
230
|
+
Some(pane),
|
|
231
|
+
Some(current_endpoint),
|
|
232
|
+
scoped.or(Some(current_endpoint)),
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#[test]
|
|
237
|
+
fn worker_caller_provider_is_generated_and_scoped_to_provider_invocation() {
|
|
238
|
+
let (env, env_unset) = spawned_caller_env();
|
|
239
|
+
let argv = ["pi".to_string(), "--help".to_string()];
|
|
240
|
+
let cwd = Path::new("/tmp/worker");
|
|
241
|
+
let wrapper = worker_shell_wrapper_command(&argv, cwd, &env, &env_unset, "pi");
|
|
242
|
+
let cold_start = super::shell_command(&argv, cwd, &env, &env_unset);
|
|
243
|
+
assert_eq!(env.get(CALLER_PROVIDER_ENV).map(String::as_str), Some("pi"));
|
|
244
|
+
assert!(!env.contains_key(CALLER_PANE_ENV));
|
|
245
|
+
assert!(!env.contains_key(CALLER_ENDPOINT_ENV));
|
|
246
|
+
assert_caller_unset_and_assignment_once(&wrapper);
|
|
247
|
+
assert_caller_unset_and_assignment_once(&cold_start);
|
|
248
|
+
assert!(
|
|
249
|
+
cold_start.contains("exec"),
|
|
250
|
+
"cold-start shell_command must exec the provider: {cold_start}"
|
|
251
|
+
);
|
|
252
|
+
let wrapper_tail = wrapper.split_once("; rc=$?").map(|(_, tail)| tail).unwrap_or("");
|
|
253
|
+
assert!(
|
|
254
|
+
!wrapper_tail.contains("TEAM_AGENT_CALLER_"),
|
|
255
|
+
"provider-exit tail must not retain caller context: {wrapper_tail}"
|
|
256
|
+
);
|
|
257
|
+
assert!(
|
|
258
|
+
!cold_start.contains("; rc=$?"),
|
|
259
|
+
"cold-start exec path has no inert tail: {cold_start}"
|
|
260
|
+
);
|
|
261
|
+
let tmux = "/tmp/tmux.sock,123,0";
|
|
262
|
+
let pane = "%1";
|
|
263
|
+
assert_eq!(
|
|
264
|
+
resolve_expanded_caller(&wrapper, tmux, pane, Some("/tmp/tmux.sock")),
|
|
265
|
+
CallerProviderResolution::Valid(Provider::Pi)
|
|
266
|
+
);
|
|
267
|
+
assert_eq!(
|
|
268
|
+
resolve_expanded_caller(&cold_start, tmux, pane, Some("/tmp/tmux.sock")),
|
|
269
|
+
CallerProviderResolution::Valid(Provider::Pi)
|
|
270
|
+
);
|
|
271
|
+
// Preserve the context generated in pane %1; only the caller moves to %2.
|
|
272
|
+
let (provider, context_pane, context_endpoint) = expand_caller_allowlist(&wrapper, tmux, pane);
|
|
273
|
+
assert_eq!(context_pane, "%1");
|
|
274
|
+
assert_eq!(
|
|
275
|
+
caller_provider_resolution_from_values(
|
|
276
|
+
Some(&provider),
|
|
277
|
+
Some(&context_pane),
|
|
278
|
+
Some(&context_endpoint),
|
|
279
|
+
None,
|
|
280
|
+
None,
|
|
281
|
+
Some("%2"),
|
|
282
|
+
Some("/tmp/tmux.sock"),
|
|
283
|
+
Some("/tmp/tmux.sock"),
|
|
284
|
+
),
|
|
285
|
+
CallerProviderResolution::Invalid
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
#[test]
|
|
290
|
+
fn worker_caller_context_is_rewritten_per_spawn_and_not_inherited() {
|
|
291
|
+
let (parent_env, parent_unset) = spawned_caller_env();
|
|
292
|
+
let parent_cmd = worker_shell_wrapper_command(
|
|
293
|
+
&["pi".to_string()],
|
|
294
|
+
Path::new("/tmp/parent-worker"),
|
|
295
|
+
&parent_env,
|
|
296
|
+
&parent_unset,
|
|
297
|
+
"pi",
|
|
298
|
+
);
|
|
299
|
+
assert_eq!(
|
|
300
|
+
resolve_expanded_caller(&parent_cmd, "/tmp/current.sock,1,0", "%4", None),
|
|
301
|
+
CallerProviderResolution::Valid(Provider::Pi)
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
let mut child_parent = parent_env
|
|
305
|
+
.iter()
|
|
306
|
+
.map(|(key, value)| (key.clone(), value.clone()))
|
|
307
|
+
.collect::<Vec<_>>();
|
|
308
|
+
child_parent.push((CALLER_PROVIDER_ENV.to_string(), "pi".to_string()));
|
|
309
|
+
child_parent.push((CALLER_PANE_ENV.to_string(), "%4".to_string()));
|
|
310
|
+
child_parent.push((
|
|
311
|
+
CALLER_ENDPOINT_ENV.to_string(),
|
|
312
|
+
"/tmp/current.sock".to_string(),
|
|
313
|
+
));
|
|
314
|
+
let mut child_env = worker_spawn_env(
|
|
315
|
+
child_parent,
|
|
316
|
+
Path::new("/tmp/child-worker"),
|
|
317
|
+
"grandchild",
|
|
318
|
+
Some("child-team"),
|
|
319
|
+
None,
|
|
320
|
+
);
|
|
321
|
+
assert!(
|
|
322
|
+
!child_env
|
|
323
|
+
.keys()
|
|
324
|
+
.any(|key| key.starts_with("TEAM_AGENT_CALLER_")),
|
|
325
|
+
"child spawn must strip the current worker caller namespace: {child_env:?}"
|
|
326
|
+
);
|
|
327
|
+
let child_unset = isolate_worker_spawn_env(Provider::Codex, &mut child_env, Vec::<String>::new());
|
|
328
|
+
inject_current_caller_provider(&mut child_env, Provider::Codex);
|
|
329
|
+
let child_cmd = worker_shell_wrapper_command(
|
|
330
|
+
&["codex".to_string()],
|
|
331
|
+
Path::new("/tmp/child-worker"),
|
|
332
|
+
&child_env,
|
|
333
|
+
&child_unset,
|
|
334
|
+
"codex",
|
|
335
|
+
);
|
|
336
|
+
assert!(child_cmd.contains("TEAM_AGENT_CALLER_PROVIDER=codex"));
|
|
337
|
+
assert!(!child_cmd.contains("TEAM_AGENT_CALLER_PROVIDER=pi"));
|
|
338
|
+
assert_eq!(
|
|
339
|
+
resolve_expanded_caller(&child_cmd, "/tmp/child.sock,1,0", "%8", None),
|
|
340
|
+
CallerProviderResolution::Valid(Provider::Codex)
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
120
344
|
/// Build a backend over a mock runner: `default` answers every un-queued call; `queued` is drained
|
|
121
345
|
/// first. Returns the backend + the shared recorded-argv handle (read AFTER the call).
|
|
122
346
|
fn backend_with(default: MockResp, queued: Vec<MockResp>) -> (TmuxBackend, RecordedArgv) {
|
|
@@ -882,6 +1106,125 @@ fn inject_token_not_visible_in_pane_reports_capture_missing_token() {
|
|
|
882
1106
|
"U1 #7: a token that never appeared in the pane must read back as \
|
|
883
1107
|
CaptureMissingToken, not the static CaptureContainsToken false-positive"
|
|
884
1108
|
);
|
|
1109
|
+
let diag = report
|
|
1110
|
+
.submit_diagnostics
|
|
1111
|
+
.as_ref()
|
|
1112
|
+
.expect("token inject records readback diagnostics");
|
|
1113
|
+
assert_eq!(diag.last_capture_outcome, CaptureSampleOutcome::OkTokenMissing);
|
|
1114
|
+
assert_eq!(diag.token_seen_after_paste, Some(false));
|
|
1115
|
+
assert!(diag.capture_ok_count >= 1);
|
|
1116
|
+
assert_eq!(diag.capture_err_count, 0);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
#[test]
|
|
1120
|
+
fn command_basename_drops_argv_and_path() {
|
|
1121
|
+
assert_eq!(
|
|
1122
|
+
command_basename("/usr/bin/grok --help --model x").as_deref(),
|
|
1123
|
+
Some("grok")
|
|
1124
|
+
);
|
|
1125
|
+
assert_eq!(command_basename("node").as_deref(), Some("node"));
|
|
1126
|
+
assert_eq!(command_basename("").as_deref(), None);
|
|
1127
|
+
assert_eq!(command_basename(" ").as_deref(), None);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
struct InjectProbeRunner {
|
|
1131
|
+
recorded: RecordedArgv,
|
|
1132
|
+
capture: MockResp,
|
|
1133
|
+
command: String,
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
impl CommandRunner for InjectProbeRunner {
|
|
1137
|
+
fn run(&self, argv: &[String]) -> Result<CommandOutput, std::io::Error> {
|
|
1138
|
+
self.recorded.lock().unwrap().push(argv.to_vec());
|
|
1139
|
+
if argv.iter().any(|arg| arg.contains("#{pane_current_command}")) {
|
|
1140
|
+
return Ok(ok(&format!("{}\n", self.command)));
|
|
1141
|
+
}
|
|
1142
|
+
if argv.iter().any(|arg| arg.contains("#{pane_id}")) {
|
|
1143
|
+
return Ok(ok("%9\n"));
|
|
1144
|
+
}
|
|
1145
|
+
if argv.get(1).map(String::as_str) == Some("capture-pane") {
|
|
1146
|
+
return match &self.capture {
|
|
1147
|
+
MockResp::Out(output) => Ok(output.clone()),
|
|
1148
|
+
MockResp::Io(kind) => Err(std::io::Error::new(*kind, "capture unavailable")),
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
Ok(ok(""))
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
fn run_with_stdin(
|
|
1155
|
+
&self,
|
|
1156
|
+
argv: &[String],
|
|
1157
|
+
_stdin: &str,
|
|
1158
|
+
) -> Result<CommandOutput, std::io::Error> {
|
|
1159
|
+
self.run(argv)
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
fn inject_probe_backend(
|
|
1164
|
+
capture: MockResp,
|
|
1165
|
+
command: &str,
|
|
1166
|
+
) -> (TmuxBackend, RecordedArgv) {
|
|
1167
|
+
let recorded = Arc::new(Mutex::new(Vec::new()));
|
|
1168
|
+
let runner = InjectProbeRunner {
|
|
1169
|
+
recorded: Arc::clone(&recorded),
|
|
1170
|
+
capture,
|
|
1171
|
+
command: command.to_string(),
|
|
1172
|
+
};
|
|
1173
|
+
(TmuxBackend::with_runner(Box::new(runner)), recorded)
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
#[test]
|
|
1177
|
+
fn inject_capture_failure_is_distinct_from_token_missing_and_does_not_pass() {
|
|
1178
|
+
let (be, _rec) = inject_probe_backend(MockResp::Io(std::io::ErrorKind::Other), "");
|
|
1179
|
+
let report = be
|
|
1180
|
+
.inject(
|
|
1181
|
+
&Target::Pane(PaneId::new("%9")),
|
|
1182
|
+
&InjectPayload::Text("hello [team-agent-token:zzz]".to_string()),
|
|
1183
|
+
Key::Down,
|
|
1184
|
+
true,
|
|
1185
|
+
)
|
|
1186
|
+
.expect("inject runs");
|
|
1187
|
+
assert_eq!(
|
|
1188
|
+
report.inject_verification,
|
|
1189
|
+
InjectVerification::CaptureMissingToken,
|
|
1190
|
+
"capture failure must not loosen CaptureMissingToken"
|
|
1191
|
+
);
|
|
1192
|
+
let diag = report.submit_diagnostics.as_ref().expect("diag");
|
|
1193
|
+
assert_eq!(diag.last_capture_outcome, CaptureSampleOutcome::Failed);
|
|
1194
|
+
assert_eq!(diag.token_seen_after_paste, None);
|
|
1195
|
+
assert!(diag.capture_err_count >= 1);
|
|
1196
|
+
assert_eq!(diag.capture_ok_count, 0);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
#[test]
|
|
1200
|
+
fn inject_records_command_basename_without_argv() {
|
|
1201
|
+
let (be, _rec) = inject_probe_backend(
|
|
1202
|
+
MockResp::Out(ok("")),
|
|
1203
|
+
"/usr/bin/grok --help --model grok-4.6",
|
|
1204
|
+
);
|
|
1205
|
+
let report = be
|
|
1206
|
+
.inject(
|
|
1207
|
+
&Target::Pane(PaneId::new("%9")),
|
|
1208
|
+
&InjectPayload::Text("hello [team-agent-token:zzz]".to_string()),
|
|
1209
|
+
Key::Down,
|
|
1210
|
+
true,
|
|
1211
|
+
)
|
|
1212
|
+
.expect("inject runs");
|
|
1213
|
+
let diag = report.submit_diagnostics.as_ref().expect("diag");
|
|
1214
|
+
assert_eq!(diag.pane_command_basename.as_deref(), Some("grok"));
|
|
1215
|
+
assert_eq!(diag.pane_command_query, QueryOutcome::Observed);
|
|
1216
|
+
assert_eq!(diag.input_surface, InputSurfaceProbe::Input);
|
|
1217
|
+
assert_eq!(diag.target_pane_id.as_deref(), Some("%9"));
|
|
1218
|
+
assert_eq!(diag.target_pane_query_matched, Some(true));
|
|
1219
|
+
let blob = format!("{diag:?}");
|
|
1220
|
+
assert!(
|
|
1221
|
+
!blob.contains("--help") && !blob.contains("grok-4.6"),
|
|
1222
|
+
"argv must not leak into diagnostics: {blob}"
|
|
1223
|
+
);
|
|
1224
|
+
assert_eq!(
|
|
1225
|
+
report.inject_verification,
|
|
1226
|
+
InjectVerification::CaptureMissingToken
|
|
1227
|
+
);
|
|
885
1228
|
}
|
|
886
1229
|
|
|
887
1230
|
#[test]
|