docs(comments): rewrite comments across all crates to the guidelines
Sweep every first-party crate source (1956 .rs files) to the project comment guidelines: delete redundant restatements, decorative banners, change narration, and end-of-line comments; keep and tighten the crucial ones (invariants, bug rationale, SAFETY blocks, ported-source attribution). No functional code changed. Every edit is proven comment-only against the prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a separate doctest-fence check. Where removing a comment made rustfmt or clippy want to re-lay-out adjacent code, the minimal triggering comment is restored so code tokens stay byte-identical. Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy --workspace --all-targets (0 warnings). Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for these guidelines (flags banners, end-of-line comments, change narration, and commented-out code).
This commit is contained in:
1 parent
ff0fb56c67
commit
a02b555e66
1458 files changed
+10729
-21750
No files matched your search
@@ -21,10 +21,7 @@ use crate::cpu_profile::ControlError;
|
||||
const CONNECT_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
const RECONNECT_DELAY: Duration = Duration::from_millis(100);
|
||||
const MAX_RECONNECT_ATTEMPTS: u32 = 3;
|
||||
/// Interval for sending keepalive pings to detect dead connections
|
||||
const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(30);
|
||||
/// Timeout for receiving registration response from server.
|
||||
/// This prevents indefinite hangs if the server doesn't respond.
|
||||
const REGISTRATION_RESPONSE_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
/// Timeout for waiting for `LeaderReady` after a `Registered { ready: false }` response.
|
||||
///
|
||||
@@ -58,7 +55,6 @@ pub struct LeaderRegistration {
|
||||
}
|
||||
|
||||
impl LeaderRegistration {
|
||||
/// Whether the connected leader advertises the `RelaunchForUpdate` control.
|
||||
pub fn supports_relaunch(&self) -> bool {
|
||||
self.leader_capabilities
|
||||
.as_ref()
|
||||
@@ -88,8 +84,6 @@ pub struct LeaderClient {
|
||||
registration: LeaderRegistration,
|
||||
cancel: CancellationToken,
|
||||
disconnect_rx: watch::Receiver<DisconnectReason>,
|
||||
/// Last `ShuttingDown` reason received from the server, or `None` if no
|
||||
/// `ShuttingDown` message has arrived yet (unplanned disconnect or still connected).
|
||||
shutting_down_rx: watch::Receiver<Option<super::protocol::ShutdownReason>>,
|
||||
}
|
||||
|
||||
@@ -126,11 +120,8 @@ impl LeaderClient {
|
||||
let pending_control = Arc::new(Mutex::new(HashMap::new()));
|
||||
let cancel = CancellationToken::new();
|
||||
let (disconnect_tx, disconnect_rx) = watch::channel(DisconnectReason::Connected);
|
||||
// Tracks the most recent ShuttingDown reason from the server.
|
||||
// None = no ShuttingDown seen; Some(reason) = last reason received.
|
||||
let (shutting_down_tx, shutting_down_rx) =
|
||||
watch::channel::<Option<super::protocol::ShutdownReason>>(None);
|
||||
// Register with server
|
||||
let (registration, _leader_ready_at_registration) = register(
|
||||
writer,
|
||||
reader,
|
||||
@@ -174,9 +165,6 @@ impl LeaderClient {
|
||||
self.shutting_down_rx.clone()
|
||||
}
|
||||
|
||||
/// Send an ACP message payload to the leader server.
|
||||
///
|
||||
/// Returns an error if the connection has been closed.
|
||||
pub fn send(&self, payload: String) -> Result<(), ClientError> {
|
||||
self.outbound_tx
|
||||
.send(ClientMessage::Acp { payload })
|
||||
@@ -342,7 +330,6 @@ async fn register(
|
||||
disconnect_tx: watch::Sender<DisconnectReason>,
|
||||
shutting_down_tx: watch::Sender<Option<super::protocol::ShutdownReason>>,
|
||||
) -> Result<(LeaderRegistration, bool), ClientError> {
|
||||
// Send registration
|
||||
write_message(
|
||||
&mut writer,
|
||||
&ClientMessage::Register {
|
||||
@@ -353,7 +340,6 @@ async fn register(
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Wait for confirmation with timeout to prevent indefinite hangs
|
||||
let response: ServerMessage = match tokio::time::timeout(
|
||||
REGISTRATION_RESPONSE_TIMEOUT,
|
||||
read_message(&mut reader),
|
||||
@@ -428,7 +414,6 @@ async fn register(
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn read/write tasks
|
||||
let cancel_read = cancel.clone();
|
||||
let disconnect_tx_read = disconnect_tx.clone();
|
||||
let pending_control_read = pending_control.clone();
|
||||
@@ -546,8 +531,6 @@ mod tests {
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
|
||||
// --- Misbehaving-leader wire shapes (fake leaders, paused clock) ---
|
||||
//
|
||||
// `start_paused` auto-advances the client-side timeouts under test; the
|
||||
// fakes stall on cancellation, never timers, so the paused clock cannot
|
||||
// wake them (see `leader::test_support`).
|
||||
@@ -719,7 +702,6 @@ mod tests {
|
||||
|
||||
let handle = spawn_leader_server(sock_path.clone()).await.unwrap();
|
||||
|
||||
// Give server time to start
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
|
||||
let client = LeaderClient::connect(
|
||||
@@ -748,7 +730,6 @@ mod tests {
|
||||
.is_some_and(|capabilities| capabilities.control_v1)
|
||||
);
|
||||
|
||||
// Cleanup
|
||||
client.cancel();
|
||||
handle.cancel.cancel();
|
||||
}
|
||||
@@ -1079,11 +1060,9 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Send message to server (with request ID for routing)
|
||||
let test_payload = r#"{"jsonrpc":"2.0","method":"test","id":1}"#;
|
||||
client.send(test_payload.into()).unwrap();
|
||||
|
||||
// Receive it on server side - ID is now namespaced with client ID
|
||||
let payload = handle.acp_rx.recv().await.unwrap();
|
||||
// Verify it's valid JSON with a namespaced ID (format: "clientId|originalIdJson")
|
||||
let json: serde_json::Value = serde_json::from_str(&payload).unwrap();
|
||||
@@ -1099,10 +1078,9 @@ mod tests {
|
||||
let response = format!(r#"{{"jsonrpc":"2.0","result":{{}},"id":"{}"}}"#, id_str);
|
||||
handle.response_tx.send(response).unwrap();
|
||||
|
||||
// Receive on client - ID should be restored to original
|
||||
let received = client.recv().await.unwrap();
|
||||
let received_json: serde_json::Value = serde_json::from_str(&received).unwrap();
|
||||
assert_eq!(received_json["id"], 1); // Original ID restored
|
||||
assert_eq!(received_json["id"], 1);
|
||||
|
||||
client.cancel();
|
||||
handle.cancel.cancel();
|
||||
@@ -1125,13 +1103,10 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Cleanup
|
||||
client.cancel();
|
||||
handle.cancel.cancel();
|
||||
}
|
||||
|
||||
// --- DisconnectReason tests ---
|
||||
|
||||
#[tokio::test]
|
||||
async fn disconnect_reason_starts_connected() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
@@ -1149,7 +1124,6 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Should start as Connected
|
||||
let reason_rx = client.disconnect_reason();
|
||||
assert_eq!(*reason_rx.borrow(), DisconnectReason::Connected);
|
||||
|
||||
@@ -1176,10 +1150,8 @@ mod tests {
|
||||
|
||||
let mut reason_rx = client.disconnect_reason();
|
||||
|
||||
// Cancel the client
|
||||
client.cancel();
|
||||
|
||||
// Wait for the disconnect reason to propagate
|
||||
let _ = tokio::time::timeout(Duration::from_secs(2), reason_rx.changed()).await;
|
||||
|
||||
let reason = reason_rx.borrow().clone();
|
||||
@@ -1215,7 +1187,6 @@ mod tests {
|
||||
// Kill the server — this triggers Shutdown broadcast then socket close
|
||||
handle.cancel.cancel();
|
||||
|
||||
// Wait for the disconnect reason to propagate
|
||||
let _ = tokio::time::timeout(Duration::from_secs(2), reason_rx.changed()).await;
|
||||
|
||||
let reason = reason_rx.borrow().clone();
|
||||
@@ -1259,18 +1230,21 @@ mod tests {
|
||||
cancel_clone,
|
||||
true,
|
||||
cc,
|
||||
std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), // agent_busy
|
||||
// agent_busy
|
||||
std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
||||
crate::agent::activity::AgentActivity::default(),
|
||||
tokio::sync::watch::channel(true).1, // ready_rx
|
||||
tokio::sync::watch::channel(crate::leader::protocol::ShutdownReason::Manual).0, // shutdown_tx
|
||||
None, // use LEADER_VERSION constant
|
||||
// ready_rx
|
||||
tokio::sync::watch::channel(true).1,
|
||||
// shutdown_tx
|
||||
tokio::sync::watch::channel(crate::leader::protocol::ShutdownReason::Manual).0,
|
||||
// use LEADER_VERSION constant
|
||||
None,
|
||||
control_state,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
|
||||
// Connect via LeaderClient
|
||||
let client = LeaderClient::connect(
|
||||
sock_path,
|
||||
"test",
|
||||
@@ -1282,7 +1256,6 @@ mod tests {
|
||||
|
||||
let mut reason_rx = client.disconnect_reason();
|
||||
|
||||
// Verify initial state is Connected
|
||||
assert_eq!(*reason_rx.borrow(), DisconnectReason::Connected);
|
||||
|
||||
// Cancel the server — sends ShuttingDown then Shutdown immediately.
|
||||
@@ -1290,7 +1263,6 @@ mod tests {
|
||||
// then receives Shutdown and sets DisconnectReason::LeaderShutdown.
|
||||
cancel.cancel();
|
||||
|
||||
// Wait for disconnect reason to change
|
||||
let _ = tokio::time::timeout(Duration::from_secs(5), reason_rx.changed()).await;
|
||||
|
||||
let final_reason = reason_rx.borrow().clone();
|
||||
|
||||
@@ -18,8 +18,6 @@ use crate::util::kigi_home::kigi_home;
|
||||
/// path.
|
||||
pub const LEADER_SOCKET_ENV: &str = "KIGI_LEADER_SOCKET";
|
||||
|
||||
/// The explicit socket-path override, if [`LEADER_SOCKET_ENV`] is set and
|
||||
/// non-empty.
|
||||
fn leader_socket_override() -> Option<PathBuf> {
|
||||
std::env::var_os(LEADER_SOCKET_ENV)
|
||||
.filter(|v| !v.is_empty())
|
||||
@@ -50,7 +48,6 @@ fn resolve_lock_path(override_socket: Option<PathBuf>, root: &Path) -> PathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
/// Default leader lock path under `root` (`leader.lock`).
|
||||
pub fn default_lock_path_in(root: &Path) -> PathBuf {
|
||||
root.join("leader.lock")
|
||||
}
|
||||
@@ -61,7 +58,6 @@ pub fn default_lock_path() -> PathBuf {
|
||||
resolve_lock_path(leader_socket_override(), &kigi_home())
|
||||
}
|
||||
|
||||
/// Default leader socket path under `root` (`leader.sock`).
|
||||
pub fn default_socket_path_in(root: &Path) -> PathBuf {
|
||||
root.join("leader.sock")
|
||||
}
|
||||
@@ -131,8 +127,6 @@ pub struct LeaderLock {
|
||||
}
|
||||
|
||||
impl LeaderLock {
|
||||
/// Create a new LeaderLock using the default paths in kigi home
|
||||
/// (or the [`LEADER_SOCKET_ENV`] override when set).
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
lock_path: default_lock_path(),
|
||||
@@ -150,7 +144,6 @@ impl LeaderLock {
|
||||
&self.lock_path
|
||||
}
|
||||
|
||||
/// Open (or create) the lock file for subsequent locking operations.
|
||||
fn open_lock_file(&self) -> Result<File, LockError> {
|
||||
Ok(OpenOptions::new()
|
||||
.read(true)
|
||||
@@ -160,7 +153,6 @@ impl LeaderLock {
|
||||
.open(&self.lock_path)?)
|
||||
}
|
||||
|
||||
/// Record a successful lock acquisition in our state.
|
||||
fn mark_acquired(&mut self, file: File) {
|
||||
self.lock_file = Some(file);
|
||||
self.was_leader = true;
|
||||
@@ -185,8 +177,8 @@ impl LeaderLock {
|
||||
|
||||
/// Acquire exclusive lock, blocking until available.
|
||||
///
|
||||
/// Used by the leader process on startup. Blocks until the lock is available.
|
||||
/// After acquiring, call `write_pid()` to record the leader's PID.
|
||||
/// Used by the leader process on startup. After acquiring, call `write_pid()`
|
||||
/// to record the leader's PID.
|
||||
pub fn acquire_blocking(&mut self) -> Result<(), LockError> {
|
||||
let file = self.open_lock_file()?;
|
||||
|
||||
@@ -238,7 +230,6 @@ impl LeaderLock {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read PID from lock file (for diagnostics).
|
||||
pub fn read_pid(&self) -> Option<u32> {
|
||||
Self::read_pid_from_path(&self.lock_path)
|
||||
}
|
||||
@@ -269,13 +260,10 @@ impl LeaderLock {
|
||||
if let Some(file) = self.lock_file.take() {
|
||||
file.unlock()?;
|
||||
}
|
||||
// Clear was_leader so Drop doesn't delete files.
|
||||
// The actual leader process will clean up when it exits.
|
||||
self.was_leader = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if we currently hold the lock.
|
||||
pub fn is_held(&self) -> bool {
|
||||
self.lock_file.is_some()
|
||||
}
|
||||
@@ -303,10 +291,6 @@ impl LeaderLock {
|
||||
impl Drop for LeaderLock {
|
||||
fn drop(&mut self) {
|
||||
// Lock is automatically released when file is closed.
|
||||
// We only clean up files if was_leader is true, which means:
|
||||
// - We acquired the lock AND
|
||||
// - We did NOT call release() (which clears was_leader)
|
||||
// This ensures the spawner doesn't delete files when handing off to the leader.
|
||||
if self.was_leader {
|
||||
let _ = fs::remove_file(&self.lock_path);
|
||||
let _ = fs::remove_file(&self.sock_path);
|
||||
@@ -331,7 +315,6 @@ mod tests {
|
||||
let root = Path::new("/home/u/.kigi");
|
||||
let override_sock = PathBuf::from("/home/u/.kigi/leader-branch.sock");
|
||||
|
||||
// With an override, the path is taken verbatim.
|
||||
assert_eq!(
|
||||
resolve_socket_path(Some(override_sock.clone()), root),
|
||||
override_sock
|
||||
@@ -379,7 +362,8 @@ mod tests {
|
||||
let mut lock2 = test_lock(&temp);
|
||||
|
||||
assert!(lock1.try_acquire().unwrap());
|
||||
assert!(!lock2.try_acquire().unwrap()); // Should return false, not error
|
||||
// Should return false, not error
|
||||
assert!(!lock2.try_acquire().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -433,7 +417,6 @@ mod tests {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let mut lock = test_lock(&temp);
|
||||
|
||||
// Create socket file
|
||||
fs::write(&lock.sock_path, "").unwrap();
|
||||
assert!(lock.sock_path.exists());
|
||||
|
||||
@@ -449,7 +432,6 @@ mod tests {
|
||||
let mut lock = test_lock(&temp);
|
||||
|
||||
lock.try_acquire().unwrap();
|
||||
// Should not error even if socket doesn't exist
|
||||
lock.cleanup_socket().unwrap();
|
||||
}
|
||||
|
||||
@@ -493,10 +475,8 @@ mod tests {
|
||||
assert!(lock.try_acquire().unwrap());
|
||||
lock.release().unwrap();
|
||||
|
||||
// Drop should NOT delete the socket file
|
||||
drop(lock);
|
||||
|
||||
// Socket file should still exist (leader would still be using it)
|
||||
assert!(
|
||||
temp.path().join("leader.sock").exists(),
|
||||
"Socket file should NOT be deleted after release()"
|
||||
@@ -510,7 +490,6 @@ mod tests {
|
||||
{
|
||||
let mut lock = test_lock(&temp);
|
||||
|
||||
// Create socket file
|
||||
fs::write(&lock.sock_path, "").unwrap();
|
||||
assert!(lock.sock_path.exists());
|
||||
|
||||
@@ -519,7 +498,6 @@ mod tests {
|
||||
// lock dropped here without release()
|
||||
}
|
||||
|
||||
// Socket file should be deleted
|
||||
assert!(
|
||||
!temp.path().join("leader.sock").exists(),
|
||||
"Socket file SHOULD be deleted when dropped without release()"
|
||||
@@ -585,12 +563,12 @@ mod tests {
|
||||
|
||||
assert!(lock1.try_acquire().unwrap());
|
||||
|
||||
// Release lock1 in a background thread after a short delay
|
||||
let lock_path = lock1.lock_path.clone();
|
||||
let handle = std::thread::spawn(move || {
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
lock1.release().unwrap();
|
||||
lock_path // keep the path for verification, lock1 is consumed
|
||||
// keep the path for verification, lock1 is consumed
|
||||
lock_path
|
||||
});
|
||||
|
||||
// lock2 should acquire within the timeout because lock1 is released after 200ms
|
||||
|
||||
@@ -102,7 +102,6 @@ pub fn leader_is_older_than(leader_version: &str, baseline: &str) -> bool {
|
||||
fn should_evict(leader_version: Option<&str>, client_version: &str) -> bool {
|
||||
leader_version.is_some_and(|v| leader_is_older_than(v, client_version))
|
||||
}
|
||||
/// Base delay between reconnection attempts.
|
||||
const RECONNECT_BASE_DELAY: Duration = Duration::from_secs(1);
|
||||
/// Maximum delay between reconnection attempts (caps exponential backoff).
|
||||
const RECONNECT_MAX_DELAY: Duration = Duration::from_secs(30);
|
||||
@@ -732,7 +731,6 @@ impl LeaderConnection {
|
||||
.await
|
||||
.map_err(ConnectionError::Client)
|
||||
}
|
||||
/// Returns the negotiated registration metadata for this connection.
|
||||
pub fn registration(&self) -> &LeaderRegistration {
|
||||
self.client.registration()
|
||||
}
|
||||
@@ -758,8 +756,6 @@ impl LeaderConnection {
|
||||
self.client.shutting_down_reason()
|
||||
}
|
||||
/// Decompose this connection into raw channels.
|
||||
///
|
||||
/// Useful for integration with other async code that needs direct channel access.
|
||||
pub fn into_channels(
|
||||
self,
|
||||
) -> (
|
||||
@@ -793,7 +789,7 @@ pub enum ConnectionStatus {
|
||||
/// they handled, so a fast `Reconnecting -> Connected` flip coalesced by
|
||||
/// the watch channel still registers as a reconnect.
|
||||
Connected { generation: u64 },
|
||||
/// Attempting to reconnect (includes current attempt number).
|
||||
/// Attempting to reconnect.
|
||||
Reconnecting { attempt: u32 },
|
||||
/// Reconnection failed permanently.
|
||||
Failed { error: String },
|
||||
@@ -1366,7 +1362,7 @@ async fn connect_to_leader(
|
||||
Ok(LeaderConnection { client })
|
||||
}
|
||||
/// Poll until the IPC listener at `sock_path` is reachable. A full
|
||||
/// connect would deadlock (see inline comment at the call site).
|
||||
/// connect would deadlock.
|
||||
async fn wait_for_listener_ready(sock_path: &Path) -> Result<(), ConnectionError> {
|
||||
let deadline = tokio::time::Instant::now() + SPAWN_WAIT_TIMEOUT;
|
||||
while tokio::time::Instant::now() < deadline {
|
||||
@@ -1381,7 +1377,6 @@ async fn wait_for_listener_ready(sock_path: &Path) -> Result<(), ConnectionError
|
||||
/// Wait for socket to appear and successfully connect.
|
||||
///
|
||||
/// Polls the socket path until it becomes connectable or timeout is reached.
|
||||
/// Uses exponential backoff starting from SPAWN_POLL_INTERVAL.
|
||||
pub(crate) async fn wait_for_socket_connectable(
|
||||
sock_path: &Path,
|
||||
client_type: &str,
|
||||
|
||||
@@ -5,7 +5,7 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
|
||||
use crate::cpu_profile::{ControlError, ProfileArtifactFormat};
|
||||
|
||||
const MAX_MESSAGE_SIZE: u32 = 64 * 1024 * 1024; // 64MB
|
||||
const MAX_MESSAGE_SIZE: u32 = 64 * 1024 * 1024;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ProtocolError {
|
||||
@@ -73,22 +73,14 @@ where
|
||||
}
|
||||
|
||||
/// Unique identifier for a connected client.
|
||||
///
|
||||
/// Each client gets a unique ID when connecting to the leader server.
|
||||
/// IDs are monotonically increasing and wrap around at u64::MAX.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ClientId(pub u64);
|
||||
|
||||
impl ClientId {
|
||||
/// Generate a new unique client ID.
|
||||
///
|
||||
/// Uses an atomic counter that wraps around at u64::MAX.
|
||||
/// While collisions are theoretically possible after 2^64 IDs,
|
||||
/// this is practically impossible in real-world usage.
|
||||
pub fn new() -> Self {
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
static COUNTER: AtomicU64 = AtomicU64::new(1);
|
||||
// Use wrapping_add to handle overflow gracefully
|
||||
// Never yield 0: the counter starts at 1 and skips it on wraparound.
|
||||
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
Self(if id == 0 {
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
@@ -109,16 +101,12 @@ impl Default for ClientId {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ClientMode {
|
||||
/// Stdio mode (kigi agent stdio, kigi -p) - uses local IPC.
|
||||
/// Client sends/receives ACP messages directly via IPC.
|
||||
Stdio,
|
||||
}
|
||||
|
||||
/// Client capabilities reported during registration.
|
||||
///
|
||||
/// These capabilities are used by the leader to customize behavior for each client,
|
||||
/// such as injecting settings into session requests.
|
||||
pub const LEADER_PROTOCOL_VERSION: u32 = 1;
|
||||
|
||||
/// Client capabilities reported during registration.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ClientCapabilities {
|
||||
/// Auto-approve all tool executions without confirmation (YOLO mode).
|
||||
@@ -264,7 +252,6 @@ pub enum ControlPayload {
|
||||
pub enum ClientMessage {
|
||||
Register {
|
||||
client_type: String,
|
||||
/// Client mode determines how leader handles this client's communication
|
||||
mode: ClientMode,
|
||||
#[serde(default)]
|
||||
capabilities: ClientCapabilities,
|
||||
@@ -281,14 +268,6 @@ pub enum ClientMessage {
|
||||
}
|
||||
|
||||
/// Reason for a planned leader shutdown, sent with [`ServerMessage::ShuttingDown`].
|
||||
///
|
||||
/// ## Runtime status
|
||||
///
|
||||
/// | Variant | Emitted today? | Notes |
|
||||
/// |---------|---------------|-------|
|
||||
/// | `AutoUpdate` | **Yes** — when `run_auto_update_checker` triggers shutdown | |
|
||||
/// | `Manual` | **Yes** — default for SIGTERM, test cancellation, all other paths | |
|
||||
/// | `IdleTimeout` | **No** — reserved for a future idle-timeout feature | |
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ShutdownReason {
|
||||
@@ -445,7 +424,6 @@ mod tests {
|
||||
async fn rejects_oversized_messages() {
|
||||
let (mut client, mut server) = duplex(1024);
|
||||
|
||||
// Write a length header claiming a huge message
|
||||
client
|
||||
.write_all(&(MAX_MESSAGE_SIZE + 1).to_be_bytes())
|
||||
.await
|
||||
@@ -601,8 +579,6 @@ mod tests {
|
||||
assert_eq!(unique.len(), 100);
|
||||
}
|
||||
|
||||
// --- ShuttingDown / ShutdownReason tests ---
|
||||
|
||||
#[tokio::test]
|
||||
async fn shutting_down_message_roundtrip() {
|
||||
let (mut client, mut server) = duplex(1024);
|
||||
@@ -634,7 +610,6 @@ mod tests {
|
||||
let manual = serde_json::to_string(&ShutdownReason::Manual).unwrap();
|
||||
assert_eq!(manual, "\"manual\"");
|
||||
|
||||
// Verify deserialization
|
||||
let parsed: ShutdownReason = serde_json::from_str("\"auto_update\"").unwrap();
|
||||
assert_eq!(parsed, ShutdownReason::AutoUpdate);
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ impl From<ServerMessage> for ClientOutbound {
|
||||
enum ServerMessageRef<'a> {
|
||||
Acp { payload: &'a str },
|
||||
}
|
||||
/// Write one [`ClientOutbound`] to a client connection.
|
||||
async fn write_outbound<W>(writer: &mut W, msg: &ClientOutbound) -> Result<(), ProtocolError>
|
||||
where
|
||||
W: tokio::io::AsyncWrite + Unpin,
|
||||
@@ -191,7 +190,6 @@ fn parse_response_id(json: &mut serde_json::Value) -> Option<(ClientId, String)>
|
||||
json["id"] = original_id;
|
||||
Some((ClientId(client_id), namespaced_id))
|
||||
}
|
||||
/// Extract session_id from a message's params (for session-based routing).
|
||||
fn extract_session_id(json: &serde_json::Value) -> Option<String> {
|
||||
let params = json.get("params")?;
|
||||
params
|
||||
@@ -288,16 +286,6 @@ fn is_machine_wide_broadcast_notification(json: &serde_json::Value) -> bool {
|
||||
Some("kigi/sessions/changed" | "kigi/models/update" | "kigi/mcp/servers_updated")
|
||||
)
|
||||
}
|
||||
/// Whether a payload is the `kigi/scheduled_task_inject_prompt` notification.
|
||||
///
|
||||
/// This notification tells the receiving client to enqueue AND drive a
|
||||
/// scheduled (`/loop`) cron prompt. Unlike ordinary `sessionId`-bearing
|
||||
/// notifications (which fan out to every subscriber so each renders an
|
||||
/// identical stream), it must be routed to the SINGLE session driver: if every
|
||||
/// attached client received it, each would enqueue + try to drive the same cron
|
||||
/// turn, duplicating it (phantom `#N` queue entries, competing drivers, stuck
|
||||
/// turns). The other clients render the resulting turn from the broadcast
|
||||
/// `session/update` deltas, exactly like any other turn the driver runs.
|
||||
/// The namespaced method a leader payload carries, normalizing the two ext wire
|
||||
/// forms the gateway produces:
|
||||
/// - direct: `{"method":"kigi/foo", ...}` -> `kigi/foo`
|
||||
@@ -401,7 +389,6 @@ fn extract_interaction_resolved_tool_call_id(json: &serde_json::Value) -> Option
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from)
|
||||
}
|
||||
/// Extract session_id from a prompt-complete notification.
|
||||
fn extract_session_id_from_prompt_complete(json: &serde_json::Value) -> Option<String> {
|
||||
let method = json.get("method")?.as_str()?;
|
||||
if method != "kigi/session/prompt_complete" {
|
||||
@@ -661,9 +648,6 @@ fn inject_client_identity_into_initialize(
|
||||
}
|
||||
(mutated, true)
|
||||
}
|
||||
/// Extract yolo_mode change from kigi/yolo_mode_changed notification.
|
||||
///
|
||||
/// Returns Some(yolo_mode) if this is a yolo mode change notification.
|
||||
fn extract_yolo_mode_change(json: &serde_json::Value) -> Option<bool> {
|
||||
let method = json.get("method")?.as_str()?;
|
||||
if method != "kigi/yolo_mode_changed" {
|
||||
@@ -1842,8 +1826,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
/// A non-JSON payload (`json = None`) is never parsed or re-serialized —
|
||||
/// it passes through untouched, matching the old per-helper parse-failure
|
||||
/// behavior.
|
||||
/// it passes through untouched.
|
||||
#[test]
|
||||
fn outbound_payload_non_json_passthrough() {
|
||||
let original = "not json".to_string();
|
||||
@@ -1987,7 +1970,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Helper to connect and register a client, returning the split stream.
|
||||
async fn connect_and_register(
|
||||
sock_path: &std::path::Path,
|
||||
client_type: &str,
|
||||
@@ -5127,7 +5109,7 @@ mod tests {
|
||||
/// `session/new`/`session/load` response that registers the subscriber) must
|
||||
/// still be cached, so the FIRST client to attach gets the modal replayed.
|
||||
/// Regression for the "entered the session, modal never appears, turn stuck
|
||||
/// Waiting" bug — the cache insert used to be gated on an existing subscriber.
|
||||
/// Waiting" bug — the cache insert must not be gated on an existing subscriber.
|
||||
#[tokio::test]
|
||||
async fn interaction_raised_with_no_subscriber_is_cached_and_replayed_on_first_attach() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
|
||||
@@ -106,7 +106,6 @@ async fn serve_client(
|
||||
cancel: &CancellationToken,
|
||||
) {
|
||||
let (mut reader, mut writer) = tokio::io::split(stream);
|
||||
/// A `Registered` with `client_id: 1` and the given shape.
|
||||
fn registered(
|
||||
ready: bool,
|
||||
versions: &FakeVersions,
|
||||
|
||||
@@ -53,8 +53,7 @@ mod windows_impl {
|
||||
}
|
||||
|
||||
impl LeaderStream {
|
||||
/// Connect to a listener at `path`. The path is translated to a
|
||||
/// named-pipe name and `ClientOptions::open` is used.
|
||||
/// Connect to a listener at `path`.
|
||||
pub async fn connect<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
use tokio::net::windows::named_pipe::ClientOptions;
|
||||
|
||||
|
||||
Reference in new issue
Block a user