@team-agent/installer 0.5.66 → 0.5.68
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 +8 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +1 -0
- package/crates/team-agent/src/cli/adapters.rs +5 -0
- package/crates/team-agent/src/cli/diagnose.rs +82 -0
- package/crates/team-agent/src/cli/emit.rs +3 -3
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +21 -7
- package/crates/team-agent/src/cli/leaders.rs +125 -1
- package/crates/team-agent/src/cli/mod.rs +20 -0
- package/crates/team-agent/src/cli/send/presentation.rs +7 -1
- package/crates/team-agent/src/cli/spec.rs +2 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
- package/crates/team-agent/src/cli/tests/base.rs +26 -3
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
- package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
- package/crates/team-agent/src/communication_mode/mod.rs +6 -4
- package/crates/team-agent/src/compiler.rs +59 -0
- package/crates/team-agent/src/coordinator/backoff.rs +55 -0
- package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
- package/crates/team-agent/src/coordinator/health.rs +173 -0
- package/crates/team-agent/src/coordinator/mod.rs +31 -0
- package/crates/team-agent/src/coordinator/orphan.rs +31 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
- package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
- package/crates/team-agent/src/coordinator/tick.rs +133 -22
- package/crates/team-agent/src/coordinator/types.rs +49 -0
- package/crates/team-agent/src/db/message_store.rs +39 -5
- package/crates/team-agent/src/layout/worker_env.rs +20 -3
- package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
- package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
- package/crates/team-agent/src/leader/registry.rs +65 -0
- package/crates/team-agent/src/leader/start.rs +920 -63
- package/crates/team-agent/src/lifecycle/display.rs +56 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -12
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +62 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +59 -0
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
- package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -456
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
- package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +62 -2
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +139 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
- package/crates/team-agent/src/lifecycle/launch.rs +99 -29
- package/crates/team-agent/src/lifecycle/lock.rs +42 -1
- package/crates/team-agent/src/lifecycle/mod.rs +11 -0
- package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +124 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +369 -11
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
- package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
- package/crates/team-agent/src/lifecycle/restart.rs +29 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
- package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
- package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +2 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
- package/crates/team-agent/src/lifecycle/tests.rs +23 -13
- package/crates/team-agent/src/lifecycle/types.rs +61 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +85 -11
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
- package/crates/team-agent/src/mcp_server/tests.rs +196 -2
- package/crates/team-agent/src/messaging/delivery.rs +405 -40
- package/crates/team-agent/src/messaging/helpers.rs +2 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
- package/crates/team-agent/src/messaging/results.rs +4 -0
- package/crates/team-agent/src/messaging/send.rs +34 -0
- package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
- package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/types.rs +4 -1
- package/crates/team-agent/src/model/enums.rs +14 -5
- package/crates/team-agent/src/model/permissions.rs +3 -0
- package/crates/team-agent/src/os_probe.rs +73 -2
- package/crates/team-agent/src/provider/adapter.rs +231 -4
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +46 -2
- package/crates/team-agent/src/provider/classify.rs +4 -2
- package/crates/team-agent/src/provider/faults.rs +2 -1
- package/crates/team-agent/src/provider/mod.rs +11 -0
- package/crates/team-agent/src/provider/session/capture.rs +158 -20
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
- package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
- package/crates/team-agent/src/provider/session/mod.rs +18 -0
- package/crates/team-agent/src/provider/session/resume.rs +57 -0
- package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
- package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
- package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
- package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
- package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
- package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
- package/crates/team-agent/src/provider/session_scan.rs +8 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +23 -1
- package/crates/team-agent/src/state/persist.rs +301 -8
- package/crates/team-agent/src/state/repository.rs +5 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
- package/crates/team-agent/src/tmux_backend.rs +1064 -75
- package/crates/team-agent/src/transport/tests/wire.rs +28 -2
- package/crates/team-agent/src/transport.rs +248 -1
- package/npm/install.mjs +129 -73
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +33 -238
- package/skills/team-agent/command-coverage.json +31 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
- package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
|
@@ -201,7 +201,13 @@ impl crate::transport::Transport for LaneTransport {
|
|
|
201
201
|
r: crate::transport::CaptureRange,
|
|
202
202
|
) -> Result<crate::transport::CapturedText, crate::transport::TransportError> {
|
|
203
203
|
Ok(crate::transport::CapturedText {
|
|
204
|
-
|
|
204
|
+
// A fresh worker has reached the ready prompt by the time this
|
|
205
|
+
// deterministic lifecycle replay observes its pane. Returning
|
|
206
|
+
// the stable ready marker keeps the replay focused on the
|
|
207
|
+
// single-lock transaction instead of burning the provider's
|
|
208
|
+
// bounded startup-prompt polling interval on an unobservable
|
|
209
|
+
// empty screen.
|
|
210
|
+
text: "OpenAI Codex\n›".to_string(),
|
|
205
211
|
range: r,
|
|
206
212
|
})
|
|
207
213
|
}
|
|
@@ -1390,9 +1396,11 @@ fn remove_preserves_external_role_source_for_both_flag_forms() {
|
|
|
1390
1396
|
}
|
|
1391
1397
|
}
|
|
1392
1398
|
|
|
1393
|
-
// A-28
|
|
1394
|
-
//
|
|
1395
|
-
//
|
|
1399
|
+
// A-28 was: default remove must preserve registered managed markdown.
|
|
1400
|
+
// ledger.seat-supply-prereq overturned that for files under
|
|
1401
|
+
// `.team/dynamic-role-files/` (framework materialization, not user asset).
|
|
1402
|
+
// Keep the test name; flip the assertion. External --role-file is covered
|
|
1403
|
+
// by `remove_preserves_external_role_source_for_both_flag_forms`.
|
|
1396
1404
|
#[test]
|
|
1397
1405
|
fn remove_agent_preserves_registered_role_markdown_by_default() {
|
|
1398
1406
|
let ws = lanea_ws_agents(json!({
|
|
@@ -1417,15 +1425,13 @@ fn remove_agent_preserves_registered_role_markdown_by_default() {
|
|
|
1417
1425
|
None,
|
|
1418
1426
|
&LaneTransport::new("team-laneateam", &[]),
|
|
1419
1427
|
);
|
|
1420
|
-
assert!(result.is_ok(), "remove-agent should unregister the seat: {result:?}");
|
|
1421
1428
|
assert!(
|
|
1422
|
-
|
|
1423
|
-
"
|
|
1429
|
+
result.is_ok(),
|
|
1430
|
+
"remove-agent should unregister the seat: {result:?}"
|
|
1424
1431
|
);
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
"A-28: remove-agent must preserve the registered role markdown by default"
|
|
1432
|
+
assert!(
|
|
1433
|
+
!role.exists(),
|
|
1434
|
+
"managed role markdown must be gone after remove-agent so the next same-id clone is not blocked"
|
|
1429
1435
|
);
|
|
1430
1436
|
}
|
|
1431
1437
|
|
|
@@ -1455,7 +1461,35 @@ fn remove_preserves_managed_path_symlink_escape() {
|
|
|
1455
1461
|
);
|
|
1456
1462
|
assert!(result.is_ok(), "remove failed: {result:?}");
|
|
1457
1463
|
assert_eq!(std::fs::read(&external).unwrap(), b"external target\n");
|
|
1458
|
-
assert!(
|
|
1464
|
+
assert!(
|
|
1465
|
+
!managed.symlink_metadata().is_ok(),
|
|
1466
|
+
"managed symlink must be unlinked; must not follow and delete the external target"
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
#[test]
|
|
1471
|
+
fn remove_agent_deletes_unrecorded_default_managed_role() {
|
|
1472
|
+
let ws = lanea_ws_agents(json!({
|
|
1473
|
+
"alpha": { "status": "stopped", "provider": "codex", "window": "alpha" },
|
|
1474
|
+
"bravo": { "status": "stopped", "provider": "codex", "window": "bravo" }
|
|
1475
|
+
}));
|
|
1476
|
+
let role = ws.join(".team").join("dynamic-role-files").join("alpha.md");
|
|
1477
|
+
std::fs::create_dir_all(role.parent().unwrap()).unwrap();
|
|
1478
|
+
std::fs::write(&role, b"unrecorded default managed copy\n").unwrap();
|
|
1479
|
+
|
|
1480
|
+
let result = remove_agent_with_transport(
|
|
1481
|
+
&ws,
|
|
1482
|
+
&aid("alpha"),
|
|
1483
|
+
true,
|
|
1484
|
+
true,
|
|
1485
|
+
None,
|
|
1486
|
+
&LaneTransport::new("team-laneateam", &[]),
|
|
1487
|
+
);
|
|
1488
|
+
assert!(result.is_ok(), "remove failed: {result:?}");
|
|
1489
|
+
assert!(
|
|
1490
|
+
!role.exists(),
|
|
1491
|
+
"default managed path must be cleared even when state.dynamic_role_file is unset"
|
|
1492
|
+
);
|
|
1459
1493
|
}
|
|
1460
1494
|
|
|
1461
1495
|
#[test]
|
|
@@ -1659,9 +1693,8 @@ fn lanea_fork_dup_target_leader_id_is_already_exists() {
|
|
|
1659
1693
|
fork_agent_with_transport(&ws, &aid("alpha"), &aid("leader"), None, false, None, &tx)
|
|
1660
1694
|
);
|
|
1661
1695
|
assert!(
|
|
1662
|
-
text.contains("
|
|
1663
|
-
"
|
|
1664
|
-
'agent id already exists: leader'; Rust skips the dup guard for the leader id and proceeds. got {text}"
|
|
1696
|
+
text.contains("unverified") || text.contains("未验证") || text.contains("refuses --as"),
|
|
1697
|
+
"codex in-window fork is unverified; leader-id duplicate is not reached. got {text}"
|
|
1665
1698
|
);
|
|
1666
1699
|
}
|
|
1667
1700
|
|
|
@@ -1679,9 +1712,11 @@ fn lanea_fork_window_already_exists_guard_before_spec_mutation() {
|
|
|
1679
1712
|
fork_agent_with_transport(&ws, &aid("alpha"), &aid("newfork"), None, false, None, &tx)
|
|
1680
1713
|
);
|
|
1681
1714
|
assert!(
|
|
1682
|
-
text.contains("
|
|
1683
|
-
|
|
1684
|
-
|
|
1715
|
+
text.contains("unverified")
|
|
1716
|
+
|| text.contains("未验证")
|
|
1717
|
+
|| text.contains("refuses --as")
|
|
1718
|
+
|| text.contains("tmux window already exists for fork target: team-laneateam:newfork"),
|
|
1719
|
+
"codex in-window fork is unverified; window-exists guard is not reached. got {text}"
|
|
1685
1720
|
);
|
|
1686
1721
|
let spec_text = std::fs::read_to_string(ws.join("team.spec.yaml")).unwrap();
|
|
1687
1722
|
assert!(
|
|
@@ -1705,9 +1740,8 @@ fn lanea_fork_gate_error_text_and_spec_rollback_on_adapter_arm() {
|
|
|
1705
1740
|
fork_agent_with_transport(&ws, &aid("alpha"), &aid("newfork"), None, false, None, &tx);
|
|
1706
1741
|
let text = format!("{result:?}");
|
|
1707
1742
|
assert!(
|
|
1708
|
-
text.contains("
|
|
1709
|
-
"
|
|
1710
|
-
fork'; Rust surfaces the generic 'capability unsupported: Codex:fork'. got {text}"
|
|
1743
|
+
text.contains("unverified") || text.contains("未验证"),
|
|
1744
|
+
"codex is unverified (any auth), not 'does not support'. got {text}"
|
|
1711
1745
|
);
|
|
1712
1746
|
let spec_text = std::fs::read_to_string(ws.join("team.spec.yaml")).unwrap();
|
|
1713
1747
|
assert!(
|
|
@@ -1736,25 +1770,16 @@ fn lanea_fork_report_session_id_is_not_pane_id() {
|
|
|
1736
1770
|
json!(discoverable_source.to_string_lossy().to_string());
|
|
1737
1771
|
crate::state::persist::save_runtime_state(&ws, &state).unwrap();
|
|
1738
1772
|
let tx = LaneTransport::new("team-laneateam", &[]);
|
|
1739
|
-
let
|
|
1740
|
-
fork_agent_with_transport(&ws, &aid("alpha"), &aid("newfork"), None, false, None, &tx)
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
"golden operations.py:399,408: report.session_id is the captured provider session id / None, NEVER the \
|
|
1746
|
-
tmux pane id; Rust returns Some(pane_id='%newfork')"
|
|
1747
|
-
);
|
|
1773
|
+
let result =
|
|
1774
|
+
fork_agent_with_transport(&ws, &aid("alpha"), &aid("newfork"), None, false, None, &tx);
|
|
1775
|
+
let err = match result {
|
|
1776
|
+
Err(error) => error.to_string(),
|
|
1777
|
+
Ok(_) => panic!("codex in-window fork is unverified and must refuse"),
|
|
1778
|
+
};
|
|
1748
1779
|
assert!(
|
|
1749
|
-
|
|
1750
|
-
"
|
|
1780
|
+
err.contains("unverified") || err.contains("未验证"),
|
|
1781
|
+
"codex must refuse as unverified; got {err}"
|
|
1751
1782
|
);
|
|
1752
|
-
let state = crate::state::persist::load_runtime_state(&ws).unwrap();
|
|
1753
|
-
let row = &state["agents"]["newfork"];
|
|
1754
|
-
assert!(row["spawned_at"]
|
|
1755
|
-
.as_str()
|
|
1756
|
-
.is_some_and(|value| chrono::DateTime::parse_from_rfc3339(value).is_ok()));
|
|
1757
|
-
assert_eq!(row["spawn_epoch"], serde_json::json!(1));
|
|
1758
1783
|
}
|
|
1759
1784
|
|
|
1760
1785
|
#[test]
|
|
@@ -2638,10 +2638,11 @@ fn fork_refuses_source_with_partial_tuple() {
|
|
|
2638
2638
|
);
|
|
2639
2639
|
let err = result.unwrap_err().to_string();
|
|
2640
2640
|
assert!(
|
|
2641
|
-
err.contains("
|
|
2641
|
+
err.contains("unverified")
|
|
2642
|
+
|| err.contains("未验证")
|
|
2642
2643
|
|| err.contains("backing")
|
|
2643
2644
|
|| err.contains("incomplete"),
|
|
2644
|
-
"0.4.6:
|
|
2645
|
+
"0.4.6/in-window: unverified providers refuse before backing check; got error={err}"
|
|
2645
2646
|
);
|
|
2646
2647
|
}
|
|
2647
2648
|
|
|
@@ -92,23 +92,31 @@ fn fork_agent_rollback_cleans_spawned_window_spec_state_and_mcp_after_post_spawn
|
|
|
92
92
|
|
|
93
93
|
assert!(
|
|
94
94
|
result.is_err(),
|
|
95
|
-
"
|
|
95
|
+
"in-window fork on this fixture must refuse before spawn; result={result:?}"
|
|
96
96
|
);
|
|
97
|
+
let err = format!("{result:?}");
|
|
97
98
|
assert!(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
err.contains("unverified") || err.contains("未验证") || err.contains("refuses --as"),
|
|
100
|
+
"codex in-window fork is unverified / --as is refused; no spawn to roll back. result={result:?}"
|
|
101
|
+
);
|
|
102
|
+
assert!(
|
|
103
|
+
transport.killed().is_empty(),
|
|
104
|
+
"unverified/in-place fork must not spawn a window to kill; killed={:?} result={result:?}",
|
|
102
105
|
transport.killed()
|
|
103
106
|
);
|
|
104
107
|
assert_eq!(
|
|
105
108
|
spec_after, spec_before,
|
|
106
|
-
"
|
|
109
|
+
"refused in-window fork must not mutate team.spec.yaml; result={result:?}"
|
|
110
|
+
);
|
|
111
|
+
assert!(
|
|
112
|
+
state_after
|
|
113
|
+
.pointer("/agents/newfork")
|
|
114
|
+
.is_none(),
|
|
115
|
+
"refused in-window fork must not create a newfork row; state={state_after}"
|
|
107
116
|
);
|
|
108
|
-
assert_failed_fork_has_only_target_tombstone(&state_after, "newfork", "rollbackteam");
|
|
109
117
|
assert!(
|
|
110
118
|
!mcp_path.exists(),
|
|
111
|
-
"
|
|
119
|
+
"refused in-window fork must not write MCP config; leaked={}",
|
|
112
120
|
mcp_path.display()
|
|
113
121
|
);
|
|
114
122
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//! purpose: worker 契约里的 MCP 工具名必须按 provider 实测书写形式渲染
|
|
2
|
+
//! contract:
|
|
3
|
+
//! claude → mcp__team_orchestrator__send_message / mcp__team_orchestrator__report_result
|
|
4
|
+
//! grok → team_orchestrator__send_message / team_orchestrator__report_result
|
|
5
|
+
//! 已验证 provider 的渲染结果不得再含 `team_orchestrator.` 点号
|
|
6
|
+
//! boundary: 只钉 compile_worker_system_prompt 字面;未验证 provider 不在此猜形式
|
|
7
|
+
|
|
8
|
+
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
9
|
+
|
|
10
|
+
use crate::lifecycle::worker_command_context::{compile_worker_system_prompt, WorkerCommandAgent};
|
|
11
|
+
use crate::model::enums::Provider;
|
|
12
|
+
|
|
13
|
+
fn compiled_prompt(provider: Provider) -> String {
|
|
14
|
+
let agent = WorkerCommandAgent::from_json(
|
|
15
|
+
&serde_json::json!({
|
|
16
|
+
"id": "w",
|
|
17
|
+
"role": "developer",
|
|
18
|
+
"tools": ["mcp_team"],
|
|
19
|
+
"output_contract": {"format": "result_envelope_v1"},
|
|
20
|
+
}),
|
|
21
|
+
Some("w"),
|
|
22
|
+
provider,
|
|
23
|
+
)
|
|
24
|
+
.expect("test agent");
|
|
25
|
+
compile_worker_system_prompt(&agent).expect("compile prompt")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#[test]
|
|
29
|
+
fn claude_runtime_contract_pins_mcp_double_underscore_literals() {
|
|
30
|
+
for provider in [Provider::Claude, Provider::ClaudeCode] {
|
|
31
|
+
let prompt = compiled_prompt(provider);
|
|
32
|
+
assert!(
|
|
33
|
+
prompt.contains("mcp__team_orchestrator__report_result"),
|
|
34
|
+
"{provider:?} must render the claude transcript literal mcp__team_orchestrator__report_result; prompt={prompt}"
|
|
35
|
+
);
|
|
36
|
+
assert!(
|
|
37
|
+
prompt.contains("mcp__team_orchestrator__send_message"),
|
|
38
|
+
"{provider:?} must render the claude transcript literal mcp__team_orchestrator__send_message; prompt={prompt}"
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[test]
|
|
44
|
+
fn grok_runtime_contract_pins_server_double_underscore_literals() {
|
|
45
|
+
let prompt = compiled_prompt(Provider::Grok);
|
|
46
|
+
assert!(
|
|
47
|
+
prompt.contains("team_orchestrator__report_result"),
|
|
48
|
+
"grok must render team_orchestrator__report_result; prompt={prompt}"
|
|
49
|
+
);
|
|
50
|
+
assert!(
|
|
51
|
+
prompt.contains("team_orchestrator__send_message"),
|
|
52
|
+
"grok must render team_orchestrator__send_message; prompt={prompt}"
|
|
53
|
+
);
|
|
54
|
+
assert!(
|
|
55
|
+
!prompt.contains("mcp__team_orchestrator"),
|
|
56
|
+
"grok must not use the claude mcp__ prefix; prompt={prompt}"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[test]
|
|
61
|
+
fn verified_provider_contracts_do_not_teach_dotted_tool_names() {
|
|
62
|
+
for provider in [Provider::Claude, Provider::ClaudeCode, Provider::Grok] {
|
|
63
|
+
let prompt = compiled_prompt(provider);
|
|
64
|
+
assert!(
|
|
65
|
+
!prompt.contains("team_orchestrator."),
|
|
66
|
+
"{provider:?} contract must not contain the dotted form team_orchestrator.; prompt={prompt}"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -204,17 +204,21 @@ fn add_fork_remove_share_lifecycle_lock_behavior() {
|
|
|
204
204
|
Duration::from_millis(120),
|
|
205
205
|
Duration::from_secs(5),
|
|
206
206
|
);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
207
|
+
let fork_held = crate::lifecycle::fork_agent_with_transport(
|
|
208
|
+
&fork_workspace,
|
|
209
|
+
&aid("alpha"),
|
|
210
|
+
&fork_agent,
|
|
211
|
+
None,
|
|
212
|
+
false,
|
|
213
|
+
None,
|
|
214
|
+
&fork_transport,
|
|
215
|
+
);
|
|
216
|
+
let fork_held_text = format!("{fork_held:?}");
|
|
217
|
+
assert!(
|
|
218
|
+
fork_held_text.contains("unverified")
|
|
219
|
+
|| fork_held_text.contains("未验证")
|
|
220
|
+
|| fork_held_text.contains("LifecycleLockTimeout"),
|
|
221
|
+
"codex in-window fork is unverified (lock is not reached); got {fork_held:?}"
|
|
218
222
|
);
|
|
219
223
|
assert!(
|
|
220
224
|
fork_transport.spawns().is_empty(),
|
|
@@ -232,7 +236,7 @@ fn add_fork_remove_share_lifecycle_lock_behavior() {
|
|
|
232
236
|
);
|
|
233
237
|
drop(override_guard);
|
|
234
238
|
drop(held);
|
|
235
|
-
crate::lifecycle::fork_agent_with_transport(
|
|
239
|
+
let fork_after = crate::lifecycle::fork_agent_with_transport(
|
|
236
240
|
&fork_workspace,
|
|
237
241
|
&aid("alpha"),
|
|
238
242
|
&fork_agent,
|
|
@@ -240,14 +244,11 @@ fn add_fork_remove_share_lifecycle_lock_behavior() {
|
|
|
240
244
|
false,
|
|
241
245
|
None,
|
|
242
246
|
&fork_transport,
|
|
243
|
-
)
|
|
244
|
-
|
|
247
|
+
);
|
|
248
|
+
let fork_after_text = format!("{fork_after:?}");
|
|
245
249
|
assert!(
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
.pointer("/agents/newfork")
|
|
249
|
-
.is_some(),
|
|
250
|
-
"released fork-agent must add the fork row"
|
|
250
|
+
fork_after_text.contains("unverified") || fork_after_text.contains("未验证"),
|
|
251
|
+
"codex in-window fork stays unverified after lock release; got {fork_after:?}"
|
|
251
252
|
);
|
|
252
253
|
|
|
253
254
|
let remove_workspace = lanea_team_ws("stopped");
|
|
@@ -102,18 +102,10 @@ fn phase_b_golden_events_state_status_zero_drift() {
|
|
|
102
102
|
team_key: "teamdir",
|
|
103
103
|
lifecycle_op: phase_b_reset_discard_session,
|
|
104
104
|
});
|
|
105
|
-
|
|
106
|
-
std::fs::create_dir_all(baseline.parent().expect("baseline parent")).unwrap();
|
|
107
|
-
std::fs::write(&baseline, pretty(&actual)).unwrap();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
let expected = std::fs::read_to_string(&baseline)
|
|
111
|
-
.unwrap_or_else(|e| panic!("missing golden baseline {}: {e}", baseline.display()));
|
|
112
|
-
let expected: Value = serde_json::from_str(&expected).expect("parse golden baseline");
|
|
105
|
+
let expected = load_phase_golden(&baseline);
|
|
113
106
|
assert_eq!(
|
|
114
|
-
actual,
|
|
115
|
-
|
|
116
|
-
"phase B golden drift; update intentionally with TEAM_AGENT_UPDATE_GOLDEN=1 only after review"
|
|
107
|
+
actual, expected,
|
|
108
|
+
"phase B golden drift; surgical key edits only — do not regenerate the file"
|
|
117
109
|
);
|
|
118
110
|
}
|
|
119
111
|
|
|
@@ -126,18 +118,10 @@ fn phase_c_golden_events_state_status_zero_drift() {
|
|
|
126
118
|
team_key: "teamdir",
|
|
127
119
|
lifecycle_op: phase_b_reset_discard_session,
|
|
128
120
|
});
|
|
129
|
-
|
|
130
|
-
std::fs::create_dir_all(baseline.parent().expect("baseline parent")).unwrap();
|
|
131
|
-
std::fs::write(&baseline, pretty(&actual)).unwrap();
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
let expected = std::fs::read_to_string(&baseline)
|
|
135
|
-
.unwrap_or_else(|e| panic!("missing golden baseline {}: {e}", baseline.display()));
|
|
136
|
-
let expected: Value = serde_json::from_str(&expected).expect("parse golden baseline");
|
|
121
|
+
let expected = load_phase_golden(&baseline);
|
|
137
122
|
assert_eq!(
|
|
138
|
-
actual,
|
|
139
|
-
|
|
140
|
-
"phase C golden drift; update intentionally with TEAM_AGENT_UPDATE_GOLDEN=1 only after review"
|
|
123
|
+
actual, expected,
|
|
124
|
+
"phase C golden drift; surgical key edits only — do not regenerate the file"
|
|
141
125
|
);
|
|
142
126
|
}
|
|
143
127
|
|
|
@@ -150,18 +134,10 @@ fn phase_d_golden_events_state_status_zero_drift() {
|
|
|
150
134
|
team_key: "teamdir",
|
|
151
135
|
lifecycle_op: phase_b_reset_discard_session,
|
|
152
136
|
});
|
|
153
|
-
|
|
154
|
-
std::fs::create_dir_all(baseline.parent().expect("baseline parent")).unwrap();
|
|
155
|
-
std::fs::write(&baseline, pretty(&actual)).unwrap();
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
let expected = std::fs::read_to_string(&baseline)
|
|
159
|
-
.unwrap_or_else(|e| panic!("missing golden baseline {}: {e}", baseline.display()));
|
|
160
|
-
let expected: Value = serde_json::from_str(&expected).expect("parse golden baseline");
|
|
137
|
+
let expected = load_phase_golden(&baseline);
|
|
161
138
|
assert_eq!(
|
|
162
|
-
actual,
|
|
163
|
-
|
|
164
|
-
"phase D golden drift; update intentionally with TEAM_AGENT_UPDATE_GOLDEN=1 only after review"
|
|
139
|
+
actual, expected,
|
|
140
|
+
"phase D golden drift; surgical key edits only — do not regenerate the file"
|
|
165
141
|
);
|
|
166
142
|
}
|
|
167
143
|
|
|
@@ -174,18 +150,10 @@ fn phase_e_golden_events_state_status_zero_drift() {
|
|
|
174
150
|
team_key: "teamdir",
|
|
175
151
|
lifecycle_op: phase_b_reset_discard_session,
|
|
176
152
|
});
|
|
177
|
-
|
|
178
|
-
std::fs::create_dir_all(baseline.parent().expect("baseline parent")).unwrap();
|
|
179
|
-
std::fs::write(&baseline, pretty(&actual)).unwrap();
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
let expected = std::fs::read_to_string(&baseline)
|
|
183
|
-
.unwrap_or_else(|e| panic!("missing golden baseline {}: {e}", baseline.display()));
|
|
184
|
-
let expected: Value = serde_json::from_str(&expected).expect("parse golden baseline");
|
|
153
|
+
let expected = load_phase_golden(&baseline);
|
|
185
154
|
assert_eq!(
|
|
186
|
-
actual,
|
|
187
|
-
|
|
188
|
-
"phase E golden drift; update intentionally with TEAM_AGENT_UPDATE_GOLDEN=1 only after review"
|
|
155
|
+
actual, expected,
|
|
156
|
+
"phase E golden drift; surgical key edits only — do not regenerate the file"
|
|
189
157
|
);
|
|
190
158
|
}
|
|
191
159
|
|
|
@@ -198,18 +166,39 @@ fn phase_f_golden_events_state_status_zero_drift() {
|
|
|
198
166
|
team_key: "teamdir",
|
|
199
167
|
lifecycle_op: phase_b_reset_discard_session,
|
|
200
168
|
});
|
|
201
|
-
|
|
202
|
-
std::fs::create_dir_all(baseline.parent().expect("baseline parent")).unwrap();
|
|
203
|
-
std::fs::write(&baseline, pretty(&actual)).unwrap();
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
let expected = std::fs::read_to_string(&baseline)
|
|
207
|
-
.unwrap_or_else(|e| panic!("missing golden baseline {}: {e}", baseline.display()));
|
|
208
|
-
let expected: Value = serde_json::from_str(&expected).expect("parse golden baseline");
|
|
169
|
+
let expected = load_phase_golden(&baseline);
|
|
209
170
|
assert_eq!(
|
|
210
|
-
actual,
|
|
211
|
-
|
|
212
|
-
|
|
171
|
+
actual, expected,
|
|
172
|
+
"phase F golden drift; surgical key edits only — do not regenerate the file"
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#[test]
|
|
177
|
+
#[serial_test::serial(env)]
|
|
178
|
+
fn phase_golden_unbound_not_ready_requires_claim_leader() {
|
|
179
|
+
// 已废除的行为:旧实现只说不 ready、不说怎么恢复,把人晾在原地。此断言证明它确实没了。
|
|
180
|
+
// Independent of golden.json so a later regenerate cannot erase it.
|
|
181
|
+
// Companion status keys grok_slot ← 77bddb95 / 0c319cca; next_action ← 080903a2.
|
|
182
|
+
let compact = run_compact_status_after_quick_start();
|
|
183
|
+
let not_ready = compact
|
|
184
|
+
.get("not_ready")
|
|
185
|
+
.and_then(Value::as_object)
|
|
186
|
+
.unwrap_or_else(|| panic!("compact must carry not_ready; got {compact}"));
|
|
187
|
+
let reasons = not_ready
|
|
188
|
+
.get("reasons")
|
|
189
|
+
.and_then(Value::as_array)
|
|
190
|
+
.cloned()
|
|
191
|
+
.unwrap_or_default();
|
|
192
|
+
assert!(
|
|
193
|
+
reasons
|
|
194
|
+
.iter()
|
|
195
|
+
.any(|reason| reason.as_str() == Some("leader_receiver_unbound")),
|
|
196
|
+
"tombstone fixture must still produce leader_receiver_unbound; not_ready={not_ready:?}"
|
|
197
|
+
);
|
|
198
|
+
assert_eq!(
|
|
199
|
+
not_ready.get("next_action").and_then(Value::as_str),
|
|
200
|
+
Some("claim-leader"),
|
|
201
|
+
"when not_ready.reasons contains leader_receiver_unbound, next_action must be claim-leader; not_ready={not_ready:?}"
|
|
213
202
|
);
|
|
214
203
|
}
|
|
215
204
|
|
|
@@ -466,6 +455,59 @@ fn phase_fixture_path(phase: &str) -> PathBuf {
|
|
|
466
455
|
.join(phase)
|
|
467
456
|
}
|
|
468
457
|
|
|
458
|
+
fn load_phase_golden(path: &Path) -> Value {
|
|
459
|
+
let text = std::fs::read_to_string(path)
|
|
460
|
+
.unwrap_or_else(|e| panic!("missing golden baseline {}: {e}", path.display()));
|
|
461
|
+
let stripped = strip_full_line_json_comments(&text);
|
|
462
|
+
serde_json::from_str(&stripped).unwrap_or_else(|e| {
|
|
463
|
+
panic!(
|
|
464
|
+
"parse golden baseline {} after stripping // review comments: {e}",
|
|
465
|
+
path.display()
|
|
466
|
+
)
|
|
467
|
+
})
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
fn strip_full_line_json_comments(text: &str) -> String {
|
|
471
|
+
text.lines()
|
|
472
|
+
.filter(|line| !line.trim_start().starts_with("//"))
|
|
473
|
+
.collect::<Vec<_>>()
|
|
474
|
+
.join("\n")
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
fn run_compact_status_after_quick_start() -> Value {
|
|
478
|
+
let hermetic = HermeticTestEnv::enter("tombstone-claim-leader");
|
|
479
|
+
let _permission_mode = EnvVarGuard::set(ANCESTRY_ENV, "[]");
|
|
480
|
+
let team = two_worker_team_dir(&hermetic);
|
|
481
|
+
let workspace = team.parent().expect("workspace").to_path_buf();
|
|
482
|
+
seed_healthy_coordinator(&workspace);
|
|
483
|
+
let launch_transport = codex_ready_transport();
|
|
484
|
+
quick_start_with_transport_in_workspace_with_display(
|
|
485
|
+
&workspace,
|
|
486
|
+
&team,
|
|
487
|
+
None,
|
|
488
|
+
true,
|
|
489
|
+
None,
|
|
490
|
+
&launch_transport,
|
|
491
|
+
false,
|
|
492
|
+
)
|
|
493
|
+
.expect("tombstone fixture must quick-start");
|
|
494
|
+
let status = cmd_status(&StatusArgs {
|
|
495
|
+
agent: None,
|
|
496
|
+
workspace,
|
|
497
|
+
detail: false,
|
|
498
|
+
summary: false,
|
|
499
|
+
json: true,
|
|
500
|
+
team: Some("teamdir".to_string()),
|
|
501
|
+
});
|
|
502
|
+
match cmd_value(status) {
|
|
503
|
+
Value::Object(map) => map
|
|
504
|
+
.get("output")
|
|
505
|
+
.cloned()
|
|
506
|
+
.unwrap_or_else(|| panic!("compact status missing output")),
|
|
507
|
+
other => panic!("compact status must be a JSON object, got {other}"),
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
469
511
|
struct EnvVarGuard {
|
|
470
512
|
key: &'static str,
|
|
471
513
|
previous: Option<String>,
|
|
@@ -843,12 +885,6 @@ fn key_is_id(key: &str, text: &str) -> bool {
|
|
|
843
885
|
|| text.starts_with("msg_")
|
|
844
886
|
}
|
|
845
887
|
|
|
846
|
-
fn pretty(value: &Value) -> String {
|
|
847
|
-
let mut text = serde_json::to_string_pretty(value).unwrap();
|
|
848
|
-
text.push('\n');
|
|
849
|
-
text
|
|
850
|
-
}
|
|
851
|
-
|
|
852
888
|
#[test]
|
|
853
889
|
fn phase_golden_normalizer_maps_custom_cargo_target_deps_binary_before_tmp_aliasing() {
|
|
854
890
|
let ctx = NormalizeCtx {
|
|
@@ -1066,6 +1066,8 @@ fn provider_name(provider: Provider) -> &'static str {
|
|
|
1066
1066
|
Provider::Codex => "codex",
|
|
1067
1067
|
Provider::Copilot => "copilot",
|
|
1068
1068
|
Provider::GeminiCli => "gemini",
|
|
1069
|
+
Provider::Grok => "grok",
|
|
1070
|
+
Provider::CursorAgent => "cursor_agent",
|
|
1069
1071
|
Provider::Fake => "fake",
|
|
1070
1072
|
}
|
|
1071
1073
|
}
|