pub mod capacity;
pub mod protocol;
#[doc(hidden)]
pub mod snapshot;
mod types;
use crate::archive::CanonicalQueuedPrompt;
use crate::clock::epoch_millis;
use anyhow::{Context, Result, bail};
pub use capacity::*;
pub use protocol::*;
use serde::{Deserialize, Serialize};
pub use snapshot::*;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicI64, Ordering};
pub use types::*;
pub const MAX_FRAME_BYTES: usize = 8 * 1024 * 1024;
pub const RELAY_REPLAY_BYTE_BUDGET: usize = 4 * 1024 * 1024;
pub const RELAY_COMMAND_BYTE_BUDGET: usize = 1024 * 1024;
pub const RELAY_EVENT_BYTE_BUDGET: usize = 2 * 1024 * 1024;
pub const RELAY_STATE_BYTE_BUDGET: usize = 2 * 1024 * 1024;
pub const TERMINAL_JOURNAL_OUTPUT_BYTES: usize = 256 * 1024;
pub const RELAY_EVENT_ENVELOPE_RESERVE: usize = 8 * 1024;
pub const RELAY_TRUNCATION_FLOOR: usize = 4 * 1024;
pub const RELAY_SNAPSHOT_BYTE_BUDGET: usize = 16 * 1024 * 1024;
pub const RELAY_PROTOCOL_VERSION: u32 = 12;
pub const RELAY_WRITER_MIN_PROTOCOL_VERSION: u32 = 11;
pub const RELAY_MIN_PROTOCOL_VERSION: u32 = 1;
pub const RELAY_EVENT_GENESIS_DIGEST: &str = crate::archive::EVENT_FRONTIER_GENESIS_DIGEST;
pub const RELAY_EVENT_DIGEST_DOMAIN: &[u8] = b"hel-relay-event-v1\0";
pub const RELAY_EVENT_DIGEST_DOMAIN_V2: &[u8] = b"hel-relay-event-v2\0";
pub const RELAY_STATE_VERSION: u32 = 6;
pub const RELAY_STATE_FILE: &str = "relay-state.json";
pub const RELAY_JOURNAL_DIR: &str = "relay-journal";
pub const RESTORED_RELAY_SEED_FILE: &str = "relay-seed.json";
pub const WORKER_PID_FILE: &str = "worker.pid";
pub const RELAY_ACTIVE_SEGMENT: &str = "active.jsonl";
pub const RELAY_SEGMENT_BYTE_LIMIT: u64 = 1024 * 1024;
pub const RELAY_SNAPSHOT_LAG_BYTE_LIMIT: usize = 1024 * 1024;
pub const RELAY_HOT_EVENT_CAPACITY: usize = 32;
pub const RELAY_REPLAY_CURSOR_CAPACITY: usize = 32;
pub const NATIVE_SESSION_IDENTITY_FILE: &str = "native-session.json";
pub fn strip_hidden_prompt_context(mut text: &str) -> &str {
loop {
text = text.trim_start();
let Some(after_open) = text.strip_prefix('<') else {
return text;
};
let Some(open_end) = after_open.find('>') else {
return if reserved_hidden_context_prefix(after_open) {
""
} else {
text
};
};
let tag = &after_open[..open_end];
if !reserved_hidden_context_tag(tag) {
return text;
}
let close = format!("</{tag}>");
let after_open = &after_open[open_end + 1..];
let Some(close_start) = after_open.rfind(&close) else {
return "";
};
text = &after_open[close_start + close.len()..];
}
}
fn reserved_hidden_context_prefix(text: &str) -> bool {
text.starts_with("hel-") || text.starts_with("mj-") || text.starts_with("user_shell_command")
}
fn reserved_hidden_context_tag(tag: &str) -> bool {
((tag.starts_with("hel-") || tag.starts_with("mj-"))
&& tag
.bytes()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-'))
|| tag == "user_shell_command"
}
#[derive(Debug, Clone, Default)]
pub struct AcpActivityClock(Arc<AtomicI64>);
impl AcpActivityClock {
pub fn mark(&self) {
self.0.store(epoch_millis(), Ordering::Release);
}
pub fn last_at_ms(&self) -> Option<i64> {
let value = self.0.load(Ordering::Acquire);
(value > 0).then_some(value)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RestoredRelaySeed {
pub event_frontier: u64,
pub event_frontier_digest: String,
#[serde(default)]
pub queued_prompts: Vec<CanonicalQueuedPrompt>,
}
impl RestoredRelaySeed {
pub fn validate(&self) -> Result<()> {
validate_relay_digest(
&self.event_frontier_digest,
"restored relay event frontier digest",
)?;
if (self.event_frontier == 0) != (self.event_frontier_digest == RELAY_EVENT_GENESIS_DIGEST)
{
bail!("restored relay event frontier and genesis digest disagree");
}
Ok(())
}
}
pub fn restored_relay_seed_path(relay_root: &Path) -> PathBuf {
relay_root.join(RESTORED_RELAY_SEED_FILE)
}
pub fn clear_native_session_identity(root: &Path) -> Result<()> {
let path = root.join(NATIVE_SESSION_IDENTITY_FILE);
match std::fs::remove_file(&path) {
Ok(()) => {
#[cfg(unix)]
std::fs::File::open(root)?.sync_all()?;
Ok(())
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(error).with_context(|| format!("remove {}", path.display())),
}
}