@team-agent/installer 0.5.74 → 0.5.75
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/layout/worker_env.rs +366 -0
- package/crates/team-agent/src/leader/lease.rs +228 -32
- package/crates/team-agent/src/leader/registry.rs +177 -19
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +12 -1
- 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/tests/worker_spawn_env_red.rs +110 -31
- 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/state/persist.rs +166 -6
- package/crates/team-agent/src/state/repository.rs +36 -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]
|