@team-agent/installer 0.3.14 → 0.3.16
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 +1 -0
- package/crates/team-agent/src/cli/emit.rs +92 -11
- package/crates/team-agent/src/cli/leader.rs +2 -0
- package/crates/team-agent/src/cli/mod.rs +255 -37
- package/crates/team-agent/src/cli/send.rs +252 -0
- package/crates/team-agent/src/cli/status.rs +5 -1
- package/crates/team-agent/src/cli/status_port.rs +24 -0
- package/crates/team-agent/src/cli/tests/base.rs +21 -3
- package/crates/team-agent/src/cli/tests/divergence.rs +6 -6
- package/crates/team-agent/src/cli/tests/run_delegation.rs +15 -3
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +285 -2
- package/crates/team-agent/src/cli/tests/status_send.rs +25 -0
- package/crates/team-agent/src/cli/types.rs +32 -1
- package/crates/team-agent/src/compiler/tests.rs +2 -2
- package/crates/team-agent/src/compiler.rs +1 -1
- package/crates/team-agent/src/fake_worker.rs +32 -145
- package/crates/team-agent/src/leader/start.rs +272 -8
- package/crates/team-agent/src/leader/tests/basics.rs +1 -0
- package/crates/team-agent/src/leader/tests/identity.rs +121 -25
- package/crates/team-agent/src/leader/types.rs +9 -0
- package/crates/team-agent/src/lifecycle/display.rs +3 -3
- package/crates/team-agent/src/lifecycle/launch.rs +631 -70
- package/crates/team-agent/src/lifecycle/restart/agent.rs +137 -17
- package/crates/team-agent/src/lifecycle/restart/common.rs +38 -2
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +79 -4
- package/crates/team-agent/src/lifecycle/tests/core.rs +14 -5
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +731 -8
- package/crates/team-agent/src/lifecycle/types.rs +5 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +8 -0
- package/crates/team-agent/src/mcp_server/wire.rs +153 -3
- package/crates/team-agent/src/messaging/leader_receiver.rs +276 -43
- package/crates/team-agent/src/messaging/mod.rs +6 -3
- package/crates/team-agent/src/messaging/results.rs +240 -158
- package/crates/team-agent/src/messaging/send.rs +3 -2
- package/crates/team-agent/src/messaging/tests/e23.rs +35 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +6 -2
- package/crates/team-agent/src/messaging/tests/runtime.rs +9 -9
- package/crates/team-agent/src/os_probe.rs +11 -0
- package/crates/team-agent/src/state/persist.rs +6 -0
- package/crates/team-agent/src/tmux_backend.rs +85 -0
- package/crates/team-agent/src/transport/test_support.rs +46 -1
- package/crates/team-agent/src/transport.rs +27 -0
- package/package.json +4 -4
- package/skills/team-agent/references/recovery-runbook.md +277 -0
|
@@ -158,11 +158,40 @@ fn tmp_shutdown_workspace(tag: &str) -> PathBuf {
|
|
|
158
158
|
|
|
159
159
|
struct CleanShutdownTransport {
|
|
160
160
|
session_present: Mutex<bool>,
|
|
161
|
+
targets: Vec<PaneInfo>,
|
|
162
|
+
kill_server_called: Mutex<bool>,
|
|
163
|
+
probe_timeout_kind: Option<&'static str>,
|
|
164
|
+
targets_persist_after_kill: bool,
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
impl CleanShutdownTransport {
|
|
164
168
|
fn new() -> Self {
|
|
165
|
-
Self {
|
|
169
|
+
Self {
|
|
170
|
+
session_present: Mutex::new(true),
|
|
171
|
+
targets: Vec::new(),
|
|
172
|
+
kill_server_called: Mutex::new(false),
|
|
173
|
+
probe_timeout_kind: None,
|
|
174
|
+
targets_persist_after_kill: false,
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fn with_targets(mut self, targets: Vec<PaneInfo>) -> Self {
|
|
179
|
+
self.targets = targets;
|
|
180
|
+
self
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fn kill_server_called(&self) -> bool {
|
|
184
|
+
*self.kill_server_called.lock().unwrap()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn with_probe_timeout(mut self, probe: &'static str) -> Self {
|
|
188
|
+
self.probe_timeout_kind = Some(probe);
|
|
189
|
+
self
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
fn with_targets_persist_after_kill(mut self) -> Self {
|
|
193
|
+
self.targets_persist_after_kill = true;
|
|
194
|
+
self
|
|
166
195
|
}
|
|
167
196
|
}
|
|
168
197
|
|
|
@@ -227,7 +256,11 @@ impl Transport for CleanShutdownTransport {
|
|
|
227
256
|
}
|
|
228
257
|
|
|
229
258
|
fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
|
|
230
|
-
|
|
259
|
+
if *self.session_present.lock().unwrap() || self.targets_persist_after_kill {
|
|
260
|
+
Ok(self.targets.clone())
|
|
261
|
+
} else {
|
|
262
|
+
Ok(Vec::new())
|
|
263
|
+
}
|
|
231
264
|
}
|
|
232
265
|
|
|
233
266
|
fn has_session(&self, _session: &SessionName) -> Result<bool, TransportError> {
|
|
@@ -248,10 +281,18 @@ impl Transport for CleanShutdownTransport {
|
|
|
248
281
|
}
|
|
249
282
|
|
|
250
283
|
fn kill_session(&self, _session: &SessionName) -> Result<(), TransportError> {
|
|
284
|
+
if let Some(probe) = self.probe_timeout_kind {
|
|
285
|
+
crate::os_probe::set_probe_timeout_for_test(probe, None, 900);
|
|
286
|
+
}
|
|
251
287
|
*self.session_present.lock().unwrap() = false;
|
|
252
288
|
Ok(())
|
|
253
289
|
}
|
|
254
290
|
|
|
291
|
+
fn kill_server(&self) -> Result<(), TransportError> {
|
|
292
|
+
*self.kill_server_called.lock().unwrap() = true;
|
|
293
|
+
Ok(())
|
|
294
|
+
}
|
|
295
|
+
|
|
255
296
|
fn kill_window(&self, _target: &Target) -> Result<(), TransportError> {
|
|
256
297
|
Ok(())
|
|
257
298
|
}
|
|
@@ -260,3 +301,245 @@ impl Transport for CleanShutdownTransport {
|
|
|
260
301
|
Ok(AttachOutcome::Attached)
|
|
261
302
|
}
|
|
262
303
|
}
|
|
304
|
+
|
|
305
|
+
#[test]
|
|
306
|
+
fn lsof_cwd_timeout_is_diagnostic_not_shutdown_partial() {
|
|
307
|
+
let ws = tmp_shutdown_workspace("lsof-cwd-timeout-diagnostic");
|
|
308
|
+
crate::state::persist::save_runtime_state(
|
|
309
|
+
&ws,
|
|
310
|
+
&json!({
|
|
311
|
+
"session_name": "team-lsof-cwd-timeout",
|
|
312
|
+
"agents": {
|
|
313
|
+
"fake_impl": {
|
|
314
|
+
"status": "running",
|
|
315
|
+
"provider": "fake",
|
|
316
|
+
"window": "fake_impl"
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}),
|
|
320
|
+
)
|
|
321
|
+
.unwrap();
|
|
322
|
+
|
|
323
|
+
let out = crate::cli::lifecycle_port::shutdown_with_transport(
|
|
324
|
+
&ws,
|
|
325
|
+
true,
|
|
326
|
+
None,
|
|
327
|
+
&CleanShutdownTransport::new().with_probe_timeout("lsof_cwd"),
|
|
328
|
+
)
|
|
329
|
+
.expect("shutdown should complete");
|
|
330
|
+
|
|
331
|
+
assert_eq!(out["ok"], json!(true));
|
|
332
|
+
assert_eq!(out["status"], json!("ok"));
|
|
333
|
+
assert_eq!(out["phase"], json!(null));
|
|
334
|
+
assert_eq!(out["verification_degraded"], json!(true));
|
|
335
|
+
assert_eq!(out["probe_timeout_kind"], json!("lsof_cwd"));
|
|
336
|
+
assert_eq!(out["residuals"]["sessions"], json!([]));
|
|
337
|
+
assert_eq!(out["residuals"]["processes"], json!([]));
|
|
338
|
+
|
|
339
|
+
let events = crate::event_log::EventLog::new(&ws).tail(0).expect("events");
|
|
340
|
+
let shutdown = events
|
|
341
|
+
.iter()
|
|
342
|
+
.find(|event| {
|
|
343
|
+
event.get("event").and_then(serde_json::Value::as_str) == Some("lifecycle.shutdown")
|
|
344
|
+
})
|
|
345
|
+
.unwrap_or_else(|| panic!("missing lifecycle.shutdown event: {events:?}"));
|
|
346
|
+
assert_eq!(shutdown["status"], json!("ok"));
|
|
347
|
+
assert_eq!(shutdown["phase"], json!(null));
|
|
348
|
+
assert_eq!(shutdown["verification_degraded"], json!(true));
|
|
349
|
+
assert_eq!(shutdown["probe_timeout_kind"], json!("lsof_cwd"));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
#[test]
|
|
353
|
+
fn ps_table_timeout_still_degrades_shutdown_truth() {
|
|
354
|
+
let ws = tmp_shutdown_workspace("ps-table-timeout-partial");
|
|
355
|
+
crate::state::persist::save_runtime_state(
|
|
356
|
+
&ws,
|
|
357
|
+
&json!({
|
|
358
|
+
"session_name": "team-ps-table-timeout",
|
|
359
|
+
"agents": {
|
|
360
|
+
"fake_impl": {
|
|
361
|
+
"status": "running",
|
|
362
|
+
"provider": "fake",
|
|
363
|
+
"window": "fake_impl"
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}),
|
|
367
|
+
)
|
|
368
|
+
.unwrap();
|
|
369
|
+
|
|
370
|
+
let out = crate::cli::lifecycle_port::shutdown_with_transport(
|
|
371
|
+
&ws,
|
|
372
|
+
true,
|
|
373
|
+
None,
|
|
374
|
+
&CleanShutdownTransport::new().with_probe_timeout("ps_table"),
|
|
375
|
+
)
|
|
376
|
+
.expect("shutdown should complete");
|
|
377
|
+
|
|
378
|
+
assert_eq!(out["ok"], json!(false));
|
|
379
|
+
assert_eq!(out["status"], json!("partial"));
|
|
380
|
+
assert_eq!(out["phase"], json!("os_probe"));
|
|
381
|
+
assert_eq!(out["verification_degraded"], json!(true));
|
|
382
|
+
assert_eq!(out["probe_timeout_kind"], json!("ps_table"));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
#[test]
|
|
386
|
+
fn bounded_coordinator_stop_returns_grace_window_late_success() {
|
|
387
|
+
let ws = tmp_shutdown_workspace("late-coordinator-stop-success");
|
|
388
|
+
let report = crate::cli::lifecycle_port::stop_coordinator_bounded_with(
|
|
389
|
+
crate::coordinator::WorkspacePath::new(ws),
|
|
390
|
+
std::time::Duration::from_millis(5),
|
|
391
|
+
|_workspace| {
|
|
392
|
+
std::thread::sleep(std::time::Duration::from_millis(25));
|
|
393
|
+
Ok(crate::coordinator::StopReport {
|
|
394
|
+
ok: true,
|
|
395
|
+
status: crate::coordinator::StopOutcome::Stopped,
|
|
396
|
+
pid: Some(crate::coordinator::Pid::new(12345)),
|
|
397
|
+
})
|
|
398
|
+
},
|
|
399
|
+
)
|
|
400
|
+
.expect("late result inside grace window must be returned")
|
|
401
|
+
.expect("stop result should be ok");
|
|
402
|
+
|
|
403
|
+
assert!(report.ok, "late success must not be discarded as timeout");
|
|
404
|
+
assert_eq!(report.status, crate::coordinator::StopOutcome::Stopped);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
#[test]
|
|
408
|
+
fn shutdown_outcome_late_or_postcheck_gone_is_ok_with_lsof_diagnostic() {
|
|
409
|
+
let out = crate::cli::lifecycle_port::classify_shutdown_outcome(
|
|
410
|
+
crate::cli::lifecycle_port::ShutdownOutcomeInput {
|
|
411
|
+
kill_error: false,
|
|
412
|
+
session_residuals: false,
|
|
413
|
+
process_residuals: false,
|
|
414
|
+
cleanup_truth_degraded: false,
|
|
415
|
+
coordinator_timeout: true,
|
|
416
|
+
coordinator_stop_ok: None,
|
|
417
|
+
coordinator_post_stop: crate::cli::lifecycle_port::CoordinatorStopObservation::Gone,
|
|
418
|
+
},
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
assert!(out.ok);
|
|
422
|
+
assert_eq!(out.status, "ok");
|
|
423
|
+
assert_eq!(out.phase, None);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
#[test]
|
|
427
|
+
fn shutdown_outcome_coordinator_timeout_still_running_is_timeout() {
|
|
428
|
+
let out = crate::cli::lifecycle_port::classify_shutdown_outcome(
|
|
429
|
+
crate::cli::lifecycle_port::ShutdownOutcomeInput {
|
|
430
|
+
kill_error: false,
|
|
431
|
+
session_residuals: false,
|
|
432
|
+
process_residuals: false,
|
|
433
|
+
cleanup_truth_degraded: false,
|
|
434
|
+
coordinator_timeout: true,
|
|
435
|
+
coordinator_stop_ok: None,
|
|
436
|
+
coordinator_post_stop: crate::cli::lifecycle_port::CoordinatorStopObservation::Running,
|
|
437
|
+
},
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
assert!(!out.ok);
|
|
441
|
+
assert_eq!(out.status, "timeout");
|
|
442
|
+
assert_eq!(out.phase, Some("stop_coordinator"));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
#[test]
|
|
446
|
+
fn shutdown_outcome_ps_table_degraded_still_partial_after_coordinator_gone() {
|
|
447
|
+
let out = crate::cli::lifecycle_port::classify_shutdown_outcome(
|
|
448
|
+
crate::cli::lifecycle_port::ShutdownOutcomeInput {
|
|
449
|
+
kill_error: false,
|
|
450
|
+
session_residuals: false,
|
|
451
|
+
process_residuals: false,
|
|
452
|
+
cleanup_truth_degraded: true,
|
|
453
|
+
coordinator_timeout: true,
|
|
454
|
+
coordinator_stop_ok: None,
|
|
455
|
+
coordinator_post_stop: crate::cli::lifecycle_port::CoordinatorStopObservation::Gone,
|
|
456
|
+
},
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
assert!(!out.ok);
|
|
460
|
+
assert_eq!(out.status, "partial");
|
|
461
|
+
assert_eq!(out.phase, Some("os_probe"));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
#[test]
|
|
465
|
+
fn leader_env_tmux_socket_never_kills_server_even_when_sessions_look_exclusive() {
|
|
466
|
+
let ws = tmp_shutdown_workspace("leader-env-socket-no-kill-server");
|
|
467
|
+
crate::state::persist::save_runtime_state(
|
|
468
|
+
&ws,
|
|
469
|
+
&json!({
|
|
470
|
+
"tmux_socket_source": "leader_env",
|
|
471
|
+
"agents": {
|
|
472
|
+
"fake_impl": {
|
|
473
|
+
"status": "running",
|
|
474
|
+
"provider": "fake",
|
|
475
|
+
"window": "fake_impl"
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}),
|
|
479
|
+
)
|
|
480
|
+
.unwrap();
|
|
481
|
+
let transport = CleanShutdownTransport::new().with_targets(vec![PaneInfo {
|
|
482
|
+
pane_id: PaneId::new("%1"),
|
|
483
|
+
session: SessionName::new("team-layout"),
|
|
484
|
+
window_index: Some(0),
|
|
485
|
+
window_name: Some(WindowName::new("team-w1")),
|
|
486
|
+
pane_index: Some(0),
|
|
487
|
+
tty: None,
|
|
488
|
+
current_command: None,
|
|
489
|
+
current_path: None,
|
|
490
|
+
active: true,
|
|
491
|
+
pane_pid: None,
|
|
492
|
+
leader_env: BTreeMap::new(),
|
|
493
|
+
}]);
|
|
494
|
+
|
|
495
|
+
let out = crate::cli::lifecycle_port::shutdown_with_transport(&ws, true, None, &transport)
|
|
496
|
+
.expect("shutdown should complete");
|
|
497
|
+
|
|
498
|
+
assert_eq!(out["ok"], json!(true));
|
|
499
|
+
assert_eq!(out["killed_sessions"], json!(["team-layout"]));
|
|
500
|
+
assert_eq!(out["spared_sessions"], json!([]));
|
|
501
|
+
assert!(
|
|
502
|
+
!transport.kill_server_called(),
|
|
503
|
+
"leader-env/shared socket shutdown must kill sessions individually, never kill-server"
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
#[test]
|
|
508
|
+
fn managed_leader_shutdown_never_kills_server_even_when_socket_looks_exclusive() {
|
|
509
|
+
let ws = tmp_shutdown_workspace("managed-leader-no-kill-server");
|
|
510
|
+
crate::state::persist::save_runtime_state(
|
|
511
|
+
&ws,
|
|
512
|
+
&json!({
|
|
513
|
+
"session_name": "team-current",
|
|
514
|
+
"is_external_leader": false,
|
|
515
|
+
"agents": {}
|
|
516
|
+
}),
|
|
517
|
+
)
|
|
518
|
+
.unwrap();
|
|
519
|
+
let transport = CleanShutdownTransport::new()
|
|
520
|
+
.with_targets(vec![PaneInfo {
|
|
521
|
+
pane_id: PaneId::new("%1"),
|
|
522
|
+
session: SessionName::new("team-current"),
|
|
523
|
+
window_index: Some(0),
|
|
524
|
+
window_name: Some(WindowName::new("leader")),
|
|
525
|
+
pane_index: Some(0),
|
|
526
|
+
tty: None,
|
|
527
|
+
current_command: Some("codex".to_string()),
|
|
528
|
+
current_path: None,
|
|
529
|
+
active: true,
|
|
530
|
+
pane_pid: None,
|
|
531
|
+
leader_env: BTreeMap::new(),
|
|
532
|
+
}])
|
|
533
|
+
.with_targets_persist_after_kill();
|
|
534
|
+
|
|
535
|
+
let out = crate::cli::lifecycle_port::shutdown_with_transport(&ws, true, None, &transport)
|
|
536
|
+
.expect("shutdown should complete");
|
|
537
|
+
|
|
538
|
+
assert_eq!(out["ok"], json!(true));
|
|
539
|
+
assert_eq!(out["killed_sessions"], json!(["team-current"]));
|
|
540
|
+
assert_eq!(out["spared_sessions"], json!([]));
|
|
541
|
+
assert!(
|
|
542
|
+
!transport.kill_server_called(),
|
|
543
|
+
"managed topology must clear the team session/window without kill-server"
|
|
544
|
+
);
|
|
545
|
+
}
|
|
@@ -36,6 +36,10 @@ use super::*;
|
|
|
36
36
|
for key in [
|
|
37
37
|
"team",
|
|
38
38
|
"session_name",
|
|
39
|
+
"leader_topology",
|
|
40
|
+
"is_external_leader",
|
|
41
|
+
"leader_attach_command",
|
|
42
|
+
"leader_client",
|
|
39
43
|
"tmux_session_present",
|
|
40
44
|
"leader_receiver",
|
|
41
45
|
"agents",
|
|
@@ -61,6 +65,27 @@ use super::*;
|
|
|
61
65
|
let _ = std::fs::remove_dir_all(&ws);
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
#[test]
|
|
69
|
+
fn status_port_status_reports_managed_leader_topology_and_attach_command() {
|
|
70
|
+
let ws = seed_status_workspace();
|
|
71
|
+
let mut state = crate::state::persist::load_runtime_state(&ws).unwrap();
|
|
72
|
+
if let Some(obj) = state.as_object_mut() {
|
|
73
|
+
obj.insert("is_external_leader".to_string(), json!(false));
|
|
74
|
+
obj.insert("session_name".to_string(), json!("team-current"));
|
|
75
|
+
}
|
|
76
|
+
crate::state::persist::save_runtime_state(&ws, &state).unwrap();
|
|
77
|
+
|
|
78
|
+
let v = status_port::status(&ws, /*compact=*/ true, /*detail=*/ false).expect("status");
|
|
79
|
+
|
|
80
|
+
assert_eq!(v["leader_topology"], json!("managed"));
|
|
81
|
+
assert_eq!(v["is_external_leader"], json!(false));
|
|
82
|
+
let attach = v["leader_attach_command"]
|
|
83
|
+
.as_str()
|
|
84
|
+
.expect("managed status includes attach command");
|
|
85
|
+
assert!(attach.contains("attach -t team-current:leader"), "{attach}");
|
|
86
|
+
let _ = std::fs::remove_dir_all(&ws);
|
|
87
|
+
}
|
|
88
|
+
|
|
64
89
|
#[test]
|
|
65
90
|
fn status_port_status_detail_full_keeps_uncompacted_events() {
|
|
66
91
|
// cmd_status --json --detail -> status_port::status(compact=false): the FULL dict
|
|
@@ -97,7 +97,8 @@ impl CliError {
|
|
|
97
97
|
"tmux session `{session}` already exists. It may be an active team. Do not terminate existing tmux sessions from startup; use a different team name or runtime.session_name and start again."
|
|
98
98
|
);
|
|
99
99
|
payload.next_actions = Some(vec![
|
|
100
|
-
"Use a different team name or runtime.session_name before starting again."
|
|
100
|
+
"Use a different team name or runtime.session_name before starting again."
|
|
101
|
+
.to_string(),
|
|
101
102
|
]);
|
|
102
103
|
}
|
|
103
104
|
}
|
|
@@ -201,6 +202,8 @@ pub struct LeaderLauncherArgs {
|
|
|
201
202
|
pub confirm_attach: bool,
|
|
202
203
|
/// `--attach-session <name>` / `--attach-session=<name>`。
|
|
203
204
|
pub attach_session: Option<String>,
|
|
205
|
+
/// 0.3.16 topology opt-out: keep the old external/current-pane leader launcher path.
|
|
206
|
+
pub external_leader: bool,
|
|
204
207
|
}
|
|
205
208
|
|
|
206
209
|
// =============================================================================
|
|
@@ -262,6 +265,7 @@ pub struct QuickStartArgs {
|
|
|
262
265
|
pub team_id: Option<String>,
|
|
263
266
|
pub yes: bool,
|
|
264
267
|
pub fresh: bool,
|
|
268
|
+
pub no_display: bool,
|
|
265
269
|
pub json: bool,
|
|
266
270
|
}
|
|
267
271
|
|
|
@@ -304,6 +308,33 @@ pub struct SendArgs {
|
|
|
304
308
|
pub message_id: Option<String>,
|
|
305
309
|
}
|
|
306
310
|
|
|
311
|
+
/// E23 worker-side emergency fallback for `team_orchestrator.send_message`
|
|
312
|
+
/// transport failures. This is not a general control-plane send path.
|
|
313
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
314
|
+
pub struct FallbackSendLeaderArgs {
|
|
315
|
+
pub workspace: PathBuf,
|
|
316
|
+
pub team: Option<String>,
|
|
317
|
+
pub sender: String,
|
|
318
|
+
pub task: Option<String>,
|
|
319
|
+
pub message_id: String,
|
|
320
|
+
pub content: String,
|
|
321
|
+
pub primary_error: String,
|
|
322
|
+
pub json: bool,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/// E23 worker-side emergency fallback for `team_orchestrator.report_result`
|
|
326
|
+
/// transport failures. It must still persist through the results DB.
|
|
327
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
328
|
+
pub struct FallbackReportResultArgs {
|
|
329
|
+
pub workspace: PathBuf,
|
|
330
|
+
pub team: Option<String>,
|
|
331
|
+
pub agent_id: String,
|
|
332
|
+
pub task_id: String,
|
|
333
|
+
pub result_json: String,
|
|
334
|
+
pub primary_error: String,
|
|
335
|
+
pub json: bool,
|
|
336
|
+
}
|
|
337
|
+
|
|
307
338
|
/// `allow-peer-talk`(`parser.py`): allow direct peer communication between two agents.
|
|
308
339
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
309
340
|
pub struct AllowPeerTalkArgs {
|
|
@@ -179,7 +179,7 @@ fn front_matter_non_object_errors() {
|
|
|
179
179
|
// separators=(",",":"))` with the workspace path templated to __WS__.
|
|
180
180
|
// (team-agent-public v0.2.11, /tmp/probe_compiler.py.)
|
|
181
181
|
|
|
182
|
-
const BASE_NOPROFILE_JSON: &str = r#"{"version":1,"team":{"name":"doc-team","mode":"supervisor_worker","objective":"Compile role docs.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":"gpt-5.5","tools":["fs_read","fs_list","mcp_team"],"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"implementer","role":"Implementation Engineer","provider":"codex","model":"gpt-5.5","auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Implement bounded tasks and report result_envelope_v1.","file":null},"tools":["fs_read","fs_write","execute_bash","mcp_team"],"permission_mode":"restricted","preferred_for":["implementer","Implementation Engineer"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}}],"routing":{"default_assignee":"implementer","rules":[{"id":"route-implementer","match":{"assignee":["implementer"]},"assign_to":"implementer","priority":10}]},"communication":{"protocol":"mcp_inbox","topology":"leader_centered","worker_to_worker":true,"ack_timeout_sec":60,"result_format":"result_envelope_v1","message_store":{"sqlite":".team/runtime/team.db","mirror_files":".team/messages"}},"runtime":{"backend":"tmux","display_backend":"
|
|
182
|
+
const BASE_NOPROFILE_JSON: &str = r#"{"version":1,"team":{"name":"doc-team","mode":"supervisor_worker","objective":"Compile role docs.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":"gpt-5.5","tools":["fs_read","fs_list","mcp_team"],"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"implementer","role":"Implementation Engineer","provider":"codex","model":"gpt-5.5","auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Implement bounded tasks and report result_envelope_v1.","file":null},"tools":["fs_read","fs_write","execute_bash","mcp_team"],"permission_mode":"restricted","preferred_for":["implementer","Implementation Engineer"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}}],"routing":{"default_assignee":"implementer","rules":[{"id":"route-implementer","match":{"assignee":["implementer"]},"assign_to":"implementer","priority":10}]},"communication":{"protocol":"mcp_inbox","topology":"leader_centered","worker_to_worker":true,"ack_timeout_sec":60,"result_format":"result_envelope_v1","message_store":{"sqlite":".team/runtime/team.db","mirror_files":".team/messages"}},"runtime":{"backend":"tmux","display_backend":"adaptive","session_name":"team-doc-team","auto_launch":true,"require_user_approval_before_launch":true,"max_active_agents":1,"startup_order":["implementer"],"dangerous_auto_approve":false,"fast":false,"tick_interval_sec":2,"push_min_interval_sec":60,"stuck_timeout_sec":300},"context":{"state_file":"team_state.md","artifact_dir":".team/artifacts","log_dir":".team/logs","summarization":{"worker_full_logs":"retain_outside_leader_context","state_update":"after_each_result"}},"tasks":[{"id":"task_initial","title":"Initial document-driven team task","type":"implementation","assignee":"implementer","deps":[],"acceptance":["Worker reports valid result_envelope_v1"],"status":"pending","requires_tools":["mcp_team"],"files":[],"risk":"low"}]}"#;
|
|
183
183
|
|
|
184
184
|
#[test]
|
|
185
185
|
fn compile_base_noprofile_matches_python_dict_order_and_values() {
|
|
@@ -352,7 +352,7 @@ tools:
|
|
|
352
352
|
Bravo body.
|
|
353
353
|
";
|
|
354
354
|
|
|
355
|
-
const TWO_AGENTS_JSON: &str = r#"{"version":1,"team":{"name":"doc-team","mode":"supervisor_worker","objective":"Compile role docs.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":"gpt-5.5","tools":["fs_read","fs_list","mcp_team"],"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"alpha","role":"Alpha Worker","provider":"codex","model":"gpt-5.5","auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Alpha body.","file":null},"tools":["mcp_team"],"permission_mode":"restricted","preferred_for":["alpha","Alpha Worker"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}},{"id":"bravo","role":"Bravo Worker","provider":"codex","model":"gpt-5.5","auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Bravo body.","file":null},"tools":["mcp_team"],"permission_mode":"restricted","preferred_for":["bravo","Bravo Worker"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}}],"routing":{"default_assignee":"alpha","rules":[{"id":"route-alpha","match":{"assignee":["alpha"]},"assign_to":"alpha","priority":10},{"id":"route-bravo","match":{"assignee":["bravo"]},"assign_to":"bravo","priority":10}]},"communication":{"protocol":"mcp_inbox","topology":"leader_centered","worker_to_worker":true,"ack_timeout_sec":60,"result_format":"result_envelope_v1","message_store":{"sqlite":".team/runtime/team.db","mirror_files":".team/messages"}},"runtime":{"backend":"tmux","display_backend":"
|
|
355
|
+
const TWO_AGENTS_JSON: &str = r#"{"version":1,"team":{"name":"doc-team","mode":"supervisor_worker","objective":"Compile role docs.","workspace":"__WS__"},"leader":{"id":"leader","role":"leader","provider":"codex","model":"gpt-5.5","tools":["fs_read","fs_list","mcp_team"],"context_policy":{"keep_user_thread":true,"receive_worker_outputs":"business_messages_and_short_summaries","max_worker_result_tokens":2000}},"agents":[{"id":"alpha","role":"Alpha Worker","provider":"codex","model":"gpt-5.5","auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Alpha body.","file":null},"tools":["mcp_team"],"permission_mode":"restricted","preferred_for":["alpha","Alpha Worker"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}},{"id":"bravo","role":"Bravo Worker","provider":"codex","model":"gpt-5.5","auth_mode":"subscription","working_directory":"__WS__","system_prompt":{"inline":"Bravo body.","file":null},"tools":["mcp_team"],"permission_mode":"restricted","preferred_for":["bravo","Bravo Worker"],"avoid_for":[],"output_contract":{"format":"result_envelope_v1","required_fields":["task_id","status","summary","artifacts"]}}],"routing":{"default_assignee":"alpha","rules":[{"id":"route-alpha","match":{"assignee":["alpha"]},"assign_to":"alpha","priority":10},{"id":"route-bravo","match":{"assignee":["bravo"]},"assign_to":"bravo","priority":10}]},"communication":{"protocol":"mcp_inbox","topology":"leader_centered","worker_to_worker":true,"ack_timeout_sec":60,"result_format":"result_envelope_v1","message_store":{"sqlite":".team/runtime/team.db","mirror_files":".team/messages"}},"runtime":{"backend":"tmux","display_backend":"adaptive","session_name":"team-doc-team","auto_launch":true,"require_user_approval_before_launch":true,"max_active_agents":2,"startup_order":["alpha","bravo"],"dangerous_auto_approve":false,"fast":false,"tick_interval_sec":2,"push_min_interval_sec":60,"stuck_timeout_sec":300},"context":{"state_file":"team_state.md","artifact_dir":".team/artifacts","log_dir":".team/logs","summarization":{"worker_full_logs":"retain_outside_leader_context","state_update":"after_each_result"}},"tasks":[{"id":"task_initial","title":"Initial document-driven team task","type":"implementation","assignee":"alpha","deps":[],"acceptance":["Worker reports valid result_envelope_v1"],"status":"pending","requires_tools":["mcp_team"],"files":[],"risk":"low"}]}"#;
|
|
356
356
|
|
|
357
357
|
#[test]
|
|
358
358
|
fn compile_two_agents_sorted_by_filename_with_routing_and_startup_order() {
|
|
@@ -247,7 +247,7 @@ pub fn compile_team(team_dir: &Path) -> Result<Value, ModelError> {
|
|
|
247
247
|
"display_backend",
|
|
248
248
|
Value::Str(
|
|
249
249
|
string_field(&team_meta, "display_backend")
|
|
250
|
-
.unwrap_or_else(|| "
|
|
250
|
+
.unwrap_or_else(|| "adaptive".to_string()),
|
|
251
251
|
),
|
|
252
252
|
),
|
|
253
253
|
("session_name", Value::Str(session_name(&team_meta, &team_name))),
|