项目文件夹

文件
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

167 行
6.1 KiB
Rust

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
//! Fast-initialize contract (GH #669).
//!
//! VS Code's start-on-demand MCP lifecycle races the first tool call of a
//! fresh conversation against server startup (microsoft/vscode#321150). The
//! server-side mitigation: nothing that spawns processes or opens sockets may
//! run in front of the `initialize` handshake — housekeeping (orphan sweep,
//! proxy autostart, publish throttle) is deferred onto the blocking pool.
//!
//! Two guards, real binary over real stdio JSON-RPC in an isolated HOME:
//! 1. spawn → initialize-response stays under a conservative wall-clock
//! bound (catches a future re-introduction of synchronous pre-serve work
//! such as a crash-backoff sleep or a network wait),
//! 2. a tools/call fired IMMEDIATELY after the initialized notification —
//! the exact VS Code race pattern — succeeds on the first attempt.
use std::io::{BufRead, BufReader, Write};
use std::process::{Child, Command, Stdio};
use std::sync::mpsc;
use std::time::{Duration, Instant};
/// Generous for CI (debug binary, cold cache, shared runners) yet far below
/// the pathological regressions this guards against (30s crash-loop backoff,
/// TCP timeouts, N×ps orphan sweeps in front of the handshake).
const INITIALIZE_DEADLINE: Duration = Duration::from_secs(10);
struct TestEnv {
_tmp: tempfile::TempDir,
home: std::path::PathBuf,
data: std::path::PathBuf,
project: std::path::PathBuf,
}
fn test_env() -> TestEnv {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path().join("home");
let data = tmp.path().join("data");
let project = tmp.path().join("project");
std::fs::create_dir_all(&home).unwrap();
std::fs::create_dir_all(&data).unwrap();
std::fs::create_dir_all(project.join(".git")).unwrap();
std::fs::write(project.join("hello.txt"), "hello fast init\n").unwrap();
TestEnv {
_tmp: tmp,
home,
data,
project,
}
}
#[test]
#[cfg_attr(
windows,
ignore = "HOME-override isolation is Unix-only (dirs::home_dir uses the Win32 API)"
)]
fn initialize_answers_fast_and_first_call_succeeds() {
let bin = env!("CARGO_BIN_EXE_lean-ctx");
let env = test_env();
let spawn_at = Instant::now();
let mut child: Child = Command::new(bin)
.arg("mcp")
.current_dir(&env.project)
.env("HOME", &env.home)
.env("LEAN_CTX_DATA_DIR", &env.data)
.env("CODEX_HOME", env.home.join(".codex"))
.env("LEAN_CTX_HEADLESS", "1")
// Root detection must derive from the temp project's cwd. When the
// suite itself runs inside an IDE/agent session these carry the HOST
// workspace and would hijack the project root (→ path-jail rejects
// the temp file).
.env_remove("LEAN_CTX_PROJECT_ROOT")
.env_remove("CLAUDE_PROJECT_DIR")
.env_remove("WORKSPACE_FOLDER_PATHS")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.expect("mcp server spawn");
let mut stdin = child.stdin.take().expect("child stdin");
let stdout = child.stdout.take().expect("child stdout");
let (tx, rx) = mpsc::channel::<String>();
let reader = std::thread::spawn(move || {
for line in BufReader::new(stdout).lines() {
match line {
Ok(l) => {
if tx.send(l).is_err() {
break;
}
}
Err(_) => break,
}
}
});
let recv_response = |id: u64, deadline: Duration| -> serde_json::Value {
let needle = format!("\"id\":{id}");
let until = Instant::now() + deadline;
loop {
let remaining = until
.checked_duration_since(Instant::now())
.unwrap_or_else(|| panic!("timeout waiting for JSON-RPC response id={id}"));
let line = rx
.recv_timeout(remaining)
.unwrap_or_else(|e| panic!("no response id={id} within deadline: {e}"));
if line.contains(&needle) {
return serde_json::from_str(&line)
.unwrap_or_else(|e| panic!("invalid JSON-RPC line: {e}\n{line}"));
}
}
};
let init = serde_json::json!({
"jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": { "name": "vscode", "version": "1.109.0" }
}
});
writeln!(stdin, "{init}").expect("write initialize");
let init_res = recv_response(1, INITIALIZE_DEADLINE);
let elapsed = spawn_at.elapsed();
assert!(
init_res["result"]["serverInfo"]["name"].is_string(),
"initialize must return serverInfo; got: {init_res}"
);
assert!(
elapsed < INITIALIZE_DEADLINE,
"spawn → initialize-response took {elapsed:?} (bound {INITIALIZE_DEADLINE:?}) — \
synchronous pre-serve work crept back in front of the handshake (#669)"
);
// The VS Code race pattern: initialized notification and the first tool
// call back-to-back, no grace period. It must succeed first try.
writeln!(
stdin,
"{}",
serde_json::json!({ "jsonrpc": "2.0", "method": "notifications/initialized" })
)
.expect("write initialized");
let call = serde_json::json!({
"jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": {
"name": "ctx_read",
"arguments": { "path": env.project.join("hello.txt").to_string_lossy() }
}
});
writeln!(stdin, "{call}").expect("write tools/call");
let call_res = recv_response(2, Duration::from_secs(30));
assert!(
call_res["error"].is_null(),
"first tools/call immediately after initialized must succeed; got: {call_res}"
);
let text = call_res["result"]["content"][0]["text"]
.as_str()
.unwrap_or_default();
assert!(
text.contains("hello fast init"),
"ctx_read must deliver the file on the first post-initialize call; got: {call_res}"
);
drop(stdin); // EOF → clean server shutdown
let _ = child.wait();
let _ = reader.join();
}