@team-agent/installer 0.5.65 → 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.
Files changed (208) hide show
  1. package/Cargo.lock +8 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +6 -0
  4. package/crates/team-agent/src/cli/adapters.rs +6 -1
  5. package/crates/team-agent/src/cli/diagnose.rs +82 -0
  6. package/crates/team-agent/src/cli/emit.rs +3 -3
  7. package/crates/team-agent/src/cli/grok_slot.rs +299 -0
  8. package/crates/team-agent/src/cli/leader.rs +21 -7
  9. package/crates/team-agent/src/cli/leaders.rs +125 -1
  10. package/crates/team-agent/src/cli/mod.rs +20 -0
  11. package/crates/team-agent/src/cli/send/presentation.rs +7 -1
  12. package/crates/team-agent/src/cli/spec.rs +2 -0
  13. package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
  14. package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
  15. package/crates/team-agent/src/cli/tests/base.rs +26 -3
  16. package/crates/team-agent/src/cli/tests/compile.rs +1 -1
  17. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
  18. package/crates/team-agent/src/cli/tests/mod.rs +2 -2
  19. package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -1
  20. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
  21. package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
  22. package/crates/team-agent/src/cli/tests/verb_validate.rs +1 -1
  23. package/crates/team-agent/src/communication_mode/mod.rs +6 -4
  24. package/crates/team-agent/src/compiler/tests.rs +23 -20
  25. package/crates/team-agent/src/compiler.rs +84 -5
  26. package/crates/team-agent/src/conpty/backend.rs +16 -0
  27. package/crates/team-agent/src/coordinator/backoff.rs +55 -0
  28. package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
  29. package/crates/team-agent/src/coordinator/health.rs +173 -0
  30. package/crates/team-agent/src/coordinator/mod.rs +31 -0
  31. package/crates/team-agent/src/coordinator/orphan.rs +31 -0
  32. package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
  33. package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
  34. package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
  35. package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
  36. package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
  37. package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
  38. package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
  39. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
  40. package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
  41. package/crates/team-agent/src/coordinator/tick.rs +133 -22
  42. package/crates/team-agent/src/coordinator/types.rs +49 -0
  43. package/crates/team-agent/src/db/message_store.rs +39 -5
  44. package/crates/team-agent/src/layout/worker_env.rs +20 -3
  45. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  46. package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
  47. package/crates/team-agent/src/leader/registry.rs +65 -0
  48. package/crates/team-agent/src/leader/start.rs +1080 -58
  49. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1 -1
  50. package/crates/team-agent/src/lifecycle/display.rs +56 -0
  51. package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
  52. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -14
  53. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +69 -2
  54. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +196 -2
  55. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
  56. package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
  57. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
  58. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -457
  59. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
  60. package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
  61. package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
  62. package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
  63. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
  64. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
  65. package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
  66. package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
  67. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
  68. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
  69. package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
  70. package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
  71. package/crates/team-agent/src/lifecycle/launch/spawn.rs +101 -4
  72. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +244 -48
  73. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -2
  74. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
  75. package/crates/team-agent/src/lifecycle/launch.rs +209 -43
  76. package/crates/team-agent/src/lifecycle/lock.rs +42 -1
  77. package/crates/team-agent/src/lifecycle/mod.rs +11 -0
  78. package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
  79. package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
  80. package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
  81. package/crates/team-agent/src/lifecycle/restart/agent.rs +213 -3
  82. package/crates/team-agent/src/lifecycle/restart/common.rs +373 -18
  83. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
  84. package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
  85. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +165 -15
  86. package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
  87. package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
  88. package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
  89. package/crates/team-agent/src/lifecycle/restart.rs +29 -0
  90. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1 -1
  91. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +3 -4
  92. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +1 -1
  93. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -2
  94. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +4 -4
  95. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
  96. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +159 -174
  97. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +2 -2
  98. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +15 -13
  99. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -18
  100. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +1 -1
  101. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
  102. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
  103. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
  104. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +1 -1
  105. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +1 -1
  106. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
  107. package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
  108. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
  109. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
  110. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
  111. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
  112. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -0
  113. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +3 -3
  114. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +65 -40
  115. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +20 -32
  116. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +17 -9
  117. package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
  118. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +24 -23
  119. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +98 -62
  120. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +2 -2
  121. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +1 -1
  122. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +1 -1
  123. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -1
  124. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -2
  125. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +1 -1
  126. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +1 -1
  127. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +41 -8
  128. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +3 -3
  129. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1 -1
  130. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +1 -1
  131. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -1
  132. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1 -1
  133. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1 -1
  134. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +1 -1
  135. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +60 -2
  136. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -1
  137. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -2
  138. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
  139. package/crates/team-agent/src/lifecycle/tests.rs +23 -15
  140. package/crates/team-agent/src/lifecycle/types.rs +61 -2
  141. package/crates/team-agent/src/lifecycle/worker_command_context.rs +200 -33
  142. package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
  143. package/crates/team-agent/src/mcp_server/tests.rs +196 -2
  144. package/crates/team-agent/src/messaging/delivery.rs +405 -40
  145. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  146. package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
  147. package/crates/team-agent/src/messaging/results.rs +4 -0
  148. package/crates/team-agent/src/messaging/send.rs +34 -0
  149. package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
  150. package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
  151. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
  152. package/crates/team-agent/src/messaging/tests/main_preserved.rs +1 -1
  153. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  154. package/crates/team-agent/src/messaging/types.rs +4 -1
  155. package/crates/team-agent/src/model/enums.rs +14 -5
  156. package/crates/team-agent/src/model/permissions.rs +3 -0
  157. package/crates/team-agent/src/model/spec.rs +129 -6
  158. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +3 -1
  159. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +3 -1
  160. package/crates/team-agent/src/model/testdata/team.spec.yaml +3 -1
  161. package/crates/team-agent/src/os_probe.rs +73 -2
  162. package/crates/team-agent/src/provider/adapter.rs +231 -4
  163. package/crates/team-agent/src/provider/adapters/claude.rs +5 -2
  164. package/crates/team-agent/src/provider/adapters/codex.rs +5 -2
  165. package/crates/team-agent/src/provider/adapters/copilot.rs +5 -2
  166. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
  167. package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
  168. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  169. package/crates/team-agent/src/provider/bypass_flags.rs +124 -0
  170. package/crates/team-agent/src/provider/classify.rs +4 -2
  171. package/crates/team-agent/src/provider/faults.rs +2 -1
  172. package/crates/team-agent/src/provider/mod.rs +13 -0
  173. package/crates/team-agent/src/provider/session/capture.rs +158 -20
  174. package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
  175. package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
  176. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
  177. package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
  178. package/crates/team-agent/src/provider/session/mod.rs +18 -0
  179. package/crates/team-agent/src/provider/session/resume.rs +57 -0
  180. package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
  181. package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
  182. package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
  183. package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
  184. package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
  185. package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
  186. package/crates/team-agent/src/provider/session_scan.rs +8 -0
  187. package/crates/team-agent/src/provider/submit_now.rs +94 -0
  188. package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
  189. package/crates/team-agent/src/provider/types.rs +4 -2
  190. package/crates/team-agent/src/provider/wire.rs +23 -1
  191. package/crates/team-agent/src/state/persist.rs +301 -8
  192. package/crates/team-agent/src/state/repository.rs +5 -0
  193. package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
  194. package/crates/team-agent/src/tmux_backend.rs +1083 -75
  195. package/crates/team-agent/src/transport/test_support.rs +20 -0
  196. package/crates/team-agent/src/transport/tests/wire.rs +28 -2
  197. package/crates/team-agent/src/transport.rs +265 -1
  198. package/npm/install.mjs +129 -73
  199. package/package.json +4 -4
  200. package/skills/team-agent/SKILL.md +33 -238
  201. package/skills/team-agent/command-coverage.json +31 -0
  202. package/crates/team-agent/src/lifecycle/launch/approval.rs +0 -134
  203. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
  204. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -483
  205. package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
  206. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
  207. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +0 -1187
  208. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +0 -327
@@ -0,0 +1,353 @@
1
+ //! ---
2
+ //! purpose: cursor 无假 pending;有 chatId+磁盘 marker 时 restart 走 --resume
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: cursor-fresh-no-fake-session-id
6
+ //! what: fresh spawn 不写 --session-id,_pending 空,session_id 空
7
+ //! - name: cursor-restart-resume-argv
8
+ //! what: session_id + store.db 齐时 restart 发 `--resume <chatId>`
9
+ //! - name: cursor-classify-resume-when-archive-present
10
+ //! what: classify 在 marker 存在时 Resume,缺失时 Refuse 而非静默 fresh
11
+ //! boundary:
12
+ //! - 不改 reset-agent --discard-session
13
+ //! - 不覆盖 grok/claude 三拍
14
+ //! maturity: wired
15
+ //! ---
16
+
17
+ #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
18
+
19
+ #[path = "../../../tests/support/hermetic.rs"]
20
+ mod hermetic_guard;
21
+ #[allow(dead_code)]
22
+ fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
23
+
24
+ use std::path::{Path, PathBuf};
25
+ use std::sync::atomic::{AtomicU64, Ordering};
26
+
27
+ use crate::lifecycle::restart::classify_restart_plan_with_resume_validation;
28
+ use crate::lifecycle::types::ResumeDecision;
29
+ use crate::provider::session_scan::cursor::{
30
+ cursor_session_archive_present, cursor_session_dir,
31
+ };
32
+ use crate::state::persist::{load_runtime_state, save_runtime_state};
33
+ use serial_test::serial;
34
+ use crate::model::ids::AgentId;
35
+ use team_agent::lifecycle::{
36
+ fork_agent_with_transport, quick_start_with_transport_in_workspace, restart_with_transport,
37
+ };
38
+ use team_agent::transport::test_support::OfflineTransport;
39
+
40
+ #[test]
41
+ #[serial(env)]
42
+ fn cursor_fresh_spawn_does_not_invent_session_id() {
43
+ let (ws, _home, _guard, team) = seed_cursor("pending");
44
+ let transport = OfflineTransport::new();
45
+ quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &transport)
46
+ .expect("cursor fresh start");
47
+ let argv = first_spawn_argv(&transport);
48
+ assert!(
49
+ argv_flag(&argv, "--session-id").is_none(),
50
+ "cursor must not invent --session-id; argv={argv:?}"
51
+ );
52
+ assert!(
53
+ argv_flag(&argv, "--resume").is_none(),
54
+ "fresh spawn must not --resume; argv={argv:?}"
55
+ );
56
+ assert!(
57
+ !argv.iter().any(|a| a == "--continue"),
58
+ "fresh spawn must not --continue; argv={argv:?}"
59
+ );
60
+ let state = load_runtime_state(&ws).expect("state");
61
+ assert_eq!(
62
+ state
63
+ .pointer("/agents/cursor_writer/_pending_session_id")
64
+ .and_then(|v| v.as_str()),
65
+ None,
66
+ "no fake pending; agent={:?}",
67
+ state.pointer("/agents/cursor_writer")
68
+ );
69
+ assert_eq!(
70
+ state
71
+ .pointer("/agents/cursor_writer/session_id")
72
+ .and_then(|v| v.as_str()),
73
+ None,
74
+ "session_id stays null until capture; agent={:?}",
75
+ state.pointer("/agents/cursor_writer")
76
+ );
77
+ }
78
+
79
+ #[test]
80
+ #[serial(env)]
81
+ fn cursor_restart_uses_resume_when_session_captured_and_archive_present() {
82
+ let (ws, home, _guard, team) = seed_cursor("resume");
83
+ let first = OfflineTransport::new();
84
+ quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &first)
85
+ .expect("initial cursor start");
86
+ let sid = "502896a1-72ba-4c53-9a86-b2da28780806";
87
+ let dir = {
88
+ let path = home
89
+ .join(".cursor")
90
+ .join("chats")
91
+ .join("00a437742b92089861da7821a62f232a")
92
+ .join(sid);
93
+ std::fs::create_dir_all(&path).unwrap();
94
+ std::fs::write(path.join("store.db"), b"").unwrap();
95
+ path
96
+ };
97
+ assert!(cursor_session_archive_present(&dir));
98
+ assert_eq!(cursor_session_dir(sid).as_deref(), Some(dir.as_path()));
99
+
100
+ let mut state = load_runtime_state(&ws).expect("load");
101
+ patch_cursor_writer(&mut state, sid, &dir, &ws);
102
+ save_runtime_state(&ws, &state).expect("save captured");
103
+
104
+ seed_healthy_coordinator(&ws);
105
+
106
+ let restart = OfflineTransport::new();
107
+ restart_with_transport(&ws, false, Some("cursortm"), &restart)
108
+ .expect("restart without --allow-fresh must resume cursor");
109
+ let argv = first_spawn_argv(&restart);
110
+ assert!(
111
+ argv_contains_adjacent(&argv, &["--resume", sid]),
112
+ "captured cursor must restart with --resume <chatId>; argv={argv:?}"
113
+ );
114
+ assert!(
115
+ argv_flag(&argv, "--session-id").is_none(),
116
+ "resume argv must not mint --session-id; argv={argv:?}"
117
+ );
118
+ assert!(
119
+ !argv.iter().any(|a| a == "--continue"),
120
+ "resume argv must not use --continue; argv={argv:?}"
121
+ );
122
+ }
123
+
124
+ #[test]
125
+ #[serial(env)]
126
+ fn cursor_classify_resumes_when_archive_present_and_refuses_when_missing() {
127
+ let base = tmp_dir("classify");
128
+ let home = tmp_dir("classify-home");
129
+ let ws = base.join("ws");
130
+ std::fs::create_dir_all(&ws).unwrap();
131
+ let _guard = HomeGuard::set(&home);
132
+ let sid = "502896a1-72ba-4c53-9a86-b2da28780806";
133
+ let dir = home.join(".cursor").join("chats").join(sid);
134
+ std::fs::create_dir_all(&dir).unwrap();
135
+ std::fs::write(dir.join("meta.json"), b"{}").unwrap();
136
+
137
+ let present = serde_json::json!({
138
+ "agents": {
139
+ "w1": {
140
+ "provider": "cursor_agent",
141
+ "status": "running",
142
+ "session_id": sid,
143
+ "spawn_cwd": ws.to_string_lossy(),
144
+ "rollout_path": dir.to_string_lossy(),
145
+ }
146
+ }
147
+ });
148
+ let plan = classify_restart_plan_with_resume_validation(Some(&ws), &present, false)
149
+ .expect("classify present");
150
+ assert_eq!(plan.decisions.len(), 1);
151
+ assert_eq!(
152
+ plan.decisions[0].decision,
153
+ ResumeDecision::Resume,
154
+ "archive present must Resume; unresumable={:?}",
155
+ plan.unresumable
156
+ );
157
+
158
+ let missing_sid = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
159
+ let missing = serde_json::json!({
160
+ "agents": {
161
+ "w1": {
162
+ "provider": "cursor_agent",
163
+ "status": "running",
164
+ "session_id": missing_sid,
165
+ "spawn_cwd": ws.to_string_lossy(),
166
+ }
167
+ }
168
+ });
169
+ let refuse = classify_restart_plan_with_resume_validation(Some(&ws), &missing, false)
170
+ .expect("classify missing");
171
+ assert_eq!(refuse.decisions.len(), 1);
172
+ assert_eq!(
173
+ refuse.decisions[0].decision,
174
+ ResumeDecision::Refuse,
175
+ "missing cursor archive must Refuse, not silent fresh; got {:?}",
176
+ refuse.decisions[0].decision
177
+ );
178
+ }
179
+
180
+ #[test]
181
+ #[serial(env)]
182
+ fn cursor_fork_agent_refuses_as_unsupported_not_unverified() {
183
+ let (ws, _home, _guard, team) = seed_cursor("fork");
184
+ let transport = OfflineTransport::new();
185
+ quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &transport)
186
+ .expect("cursor start");
187
+ let result = fork_agent_with_transport(
188
+ &ws,
189
+ &AgentId::new("cursor_writer"),
190
+ &AgentId::new("cursor_fork"),
191
+ None,
192
+ false,
193
+ Some("cursortm"),
194
+ &transport,
195
+ );
196
+ let err = match result {
197
+ Ok(report) => panic!("cursor fork must refuse; report={report:?}"),
198
+ Err(error) => error.to_string(),
199
+ };
200
+ let lower = err.to_ascii_lowercase();
201
+ assert!(
202
+ lower.contains("does not support") || err.contains("不支持"),
203
+ "fork error must be unsupported; err={err}"
204
+ );
205
+ assert!(
206
+ !lower.contains("unverified") && !err.contains("未验证"),
207
+ "must not call unverified slash fork; err={err}"
208
+ );
209
+ }
210
+
211
+ fn patch_cursor_writer(state: &mut serde_json::Value, sid: &str, dir: &Path, ws: &Path) {
212
+ let mut patched = 0u32;
213
+ let mut patch = |agent: &mut serde_json::Value| {
214
+ if let Some(obj) = agent.as_object_mut() {
215
+ obj.insert("session_id".to_string(), serde_json::json!(sid));
216
+ obj.insert(
217
+ "rollout_path".to_string(),
218
+ serde_json::json!(dir.to_string_lossy()),
219
+ );
220
+ obj.insert(
221
+ "spawn_cwd".to_string(),
222
+ serde_json::json!(ws.to_string_lossy()),
223
+ );
224
+ obj.insert("capture_state".to_string(), serde_json::json!("captured"));
225
+ patched += 1;
226
+ }
227
+ };
228
+ if let Some(agent) = state.pointer_mut("/agents/cursor_writer") {
229
+ patch(agent);
230
+ }
231
+ if let Some(teams) = state.get_mut("teams").and_then(|v| v.as_object_mut()) {
232
+ for team in teams.values_mut() {
233
+ if let Some(agent) = team
234
+ .get_mut("agents")
235
+ .and_then(|v| v.get_mut("cursor_writer"))
236
+ {
237
+ patch(agent);
238
+ }
239
+ }
240
+ }
241
+ assert!(
242
+ patched > 0,
243
+ "cursor_writer missing in state; keys={:?}",
244
+ state.as_object().map(|o| o.keys().collect::<Vec<_>>())
245
+ );
246
+ }
247
+
248
+ /// restart 的就绪门要求 `coordinator_health().ok`,而单测里 `start_coordinator`
249
+ /// spawn 的是 `std::env::current_exe()` —— 也就是 libtest 测试二进制本身,它一启动
250
+ /// 就以 `Unrecognized option: 'workspace'` 退出并变成僵尸,`pid_is_running` 的
251
+ /// `ps stat=` 一看到 `Z` 就判 not running。于是就绪门只能靠「抢在死婴被看到之前
252
+ /// 探到它」这个竞态过关,慢机/高负载上必然超时。这里按本仓既有写法(见
253
+ /// `copilot_provider_red.rs` / `restart_build_before_destroy_0540_contract.rs`)
254
+ /// 预先把 pid 文件与 metadata 指向测试进程自己,让 `start_coordinator` 走
255
+ /// `AlreadyRunning` 早返回,竞态被删掉而不是被调宽。
256
+ fn seed_healthy_coordinator(workspace: &Path) {
257
+ let workspace = crate::coordinator::WorkspacePath::new(workspace.to_path_buf());
258
+ let pid = crate::coordinator::Pid::new(std::process::id());
259
+ std::fs::create_dir_all(
260
+ crate::coordinator::coordinator_pid_path(&workspace)
261
+ .parent()
262
+ .unwrap(),
263
+ )
264
+ .unwrap();
265
+ crate::coordinator::write_coordinator_metadata(
266
+ &workspace,
267
+ pid,
268
+ crate::coordinator::MetadataSource::Boot,
269
+ )
270
+ .expect("write coordinator metadata");
271
+ std::fs::write(
272
+ crate::coordinator::coordinator_pid_path(&workspace),
273
+ pid.to_string(),
274
+ )
275
+ .expect("write coordinator pid");
276
+ }
277
+
278
+ fn seed_cursor(tag: &str) -> (PathBuf, PathBuf, HomeGuard, PathBuf) {
279
+ let ws = tmp_dir(tag);
280
+ let home = tmp_dir(&format!("{tag}-home"));
281
+ let guard = HomeGuard::set(&home);
282
+ let team = write_cursor_role(&ws);
283
+ (ws, home, guard, team)
284
+ }
285
+
286
+ fn first_spawn_argv(transport: &OfflineTransport) -> Vec<String> {
287
+ let records = transport.spawn_records();
288
+ assert!(
289
+ !records.is_empty(),
290
+ "expected a worker spawn; records={records:?}"
291
+ );
292
+ records[0].1.clone()
293
+ }
294
+
295
+ fn argv_flag(argv: &[String], flag: &str) -> Option<String> {
296
+ argv.windows(2)
297
+ .find(|pair| pair[0] == flag)
298
+ .map(|pair| pair[1].clone())
299
+ }
300
+
301
+ fn argv_contains_adjacent(hay: &[String], needle: &[&str]) -> bool {
302
+ hay.windows(needle.len())
303
+ .any(|window| window.iter().map(String::as_str).eq(needle.iter().copied()))
304
+ }
305
+
306
+ fn write_cursor_role(ws: &Path) -> PathBuf {
307
+ let team = ws.join("cursortm");
308
+ std::fs::create_dir_all(team.join("agents")).unwrap();
309
+ std::fs::write(
310
+ team.join("TEAM.md"),
311
+ "---\nname: cursortm\nobjective: cursor resume.\nprovider: cursor_agent\ndangerously_skip_permissions: true\n---\n\nTeam.\n",
312
+ )
313
+ .unwrap();
314
+ std::fs::write(
315
+ team.join("agents").join("cursor_writer.md"),
316
+ "---\nname: cursor_writer\nrole: Cursor Writer\nprovider: cursor_agent\nmodel: sonnet-4-thinking\nauth_mode: subscription\ndangerously_skip_permissions: true\ntools:\n - mcp_team\n---\n\nWorker.\n",
317
+ )
318
+ .unwrap();
319
+ team
320
+ }
321
+
322
+ struct HomeGuard {
323
+ prev: Option<String>,
324
+ }
325
+
326
+ impl HomeGuard {
327
+ fn set(home: &Path) -> Self {
328
+ let prev = std::env::var("HOME").ok();
329
+ std::env::set_var("HOME", home);
330
+ Self { prev }
331
+ }
332
+ }
333
+
334
+ impl Drop for HomeGuard {
335
+ fn drop(&mut self) {
336
+ match &self.prev {
337
+ Some(value) => std::env::set_var("HOME", value),
338
+ None => std::env::remove_var("HOME"),
339
+ }
340
+ }
341
+ }
342
+
343
+ fn tmp_dir(tag: &str) -> PathBuf {
344
+ static N: AtomicU64 = AtomicU64::new(0);
345
+ let dir = std::env::temp_dir().join(format!(
346
+ "ta-rs-cursor-resume-{tag}-{}-{}",
347
+ std::process::id(),
348
+ N.fetch_add(1, Ordering::Relaxed)
349
+ ));
350
+ let _ = std::fs::remove_dir_all(&dir);
351
+ std::fs::create_dir_all(&dir).unwrap();
352
+ std::fs::canonicalize(dir).unwrap()
353
+ }
@@ -358,7 +358,7 @@ impl DisplayFixture {
358
358
  std::fs::write(
359
359
  team.join("agents").join(format!("{worker}.md")),
360
360
  format!(
361
- "---\nname: {worker}\nrole: Adaptive fake worker {worker}\nprovider: fake\nmodel: fake\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n"
361
+ "---\nname: {worker}\nrole: Adaptive fake worker {worker}\nprovider: fake\nmodel: fake\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n"
362
362
  ),
363
363
  )
364
364
  .unwrap();
@@ -27,7 +27,7 @@ static SEQ: AtomicU32 = AtomicU32::new(0);
27
27
 
28
28
  const TEAM_MD: &str =
29
29
  "---\nname: f032team\nobjective: F032 lifecycle prompt fault probe.\nprovider: codex\n---\n\nF032.\n";
30
- const ROLE_MD: &str = "---\nname: implementer\nrole: Implementation Engineer\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nImplement.\n";
30
+ const ROLE_MD: &str = "---\nname: implementer\nrole: Implementation Engineer\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nImplement.\n";
31
31
 
32
32
  #[test]
33
33
  fn f032_launch_spawn_path_treats_startup_prompt_panic_as_best_effort() {