use crate::error::AtmError;
use crate::protocol::{FramePayload, RequestEnvelope, RequestId, ResponseEnvelope};
pub use crate::protocol::{NotificationEvent, RuntimeStatusSnapshot};
use crate::schema::AtmMessageId;
use crate::types::{AgentName, PaneId, TaskId, TeamName};
pub use atm_storage::contract::{AckTransition, Message, MessageKey, TaskState};
pub use atm_storage::{
BuiltInNudgeTemplateKind, NudgeTemplateOverrideStore, TeamNudgeTemplateOverrideMode,
TeamNudgeTemplateOverrideRow,
};
#[doc(hidden)]
pub mod sealed {
pub trait Sealed {}
}
mod mail;
mod runtime;
mod store;
pub use mail::*;
pub use runtime::*;
pub use store::*;
pub trait AtmProtocol: sealed::Sealed {
fn request_to_frame(
&self,
request_id: RequestId,
request: RequestEnvelope,
) -> Result<FramePayload, AtmError>;
fn request_from_frame(
&self,
frame: FramePayload,
) -> Result<(RequestId, RequestEnvelope), AtmError>;
fn response_to_frame(
&self,
request_id: RequestId,
response: ResponseEnvelope,
) -> Result<FramePayload, AtmError>;
fn response_from_frame(
&self,
frame: FramePayload,
) -> Result<(RequestId, ResponseEnvelope), AtmError>;
}
pub trait ClientTransport: sealed::Sealed + Send + Sync {
fn send(&self, request: RequestEnvelope) -> Result<ResponseEnvelope, AtmError>;
}
pub trait ServerTransport: sealed::Sealed {
fn serve(
&self,
dispatcher: std::sync::Arc<dyn RequestDispatcher + Send + Sync>,
) -> Result<(), AtmError>;
}
pub trait RequestDispatcher: sealed::Sealed + Send + Sync {
fn dispatch(&self, request: RequestEnvelope) -> Result<ResponseEnvelope, AtmError>;
}
pub trait StatusSource: sealed::Sealed {
fn snapshot(&self) -> Result<RuntimeStatusSnapshot, AtmError>;
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct PostSendHookEvent {
pub sender: AgentName,
pub sender_team: TeamName,
pub recipient: AgentName,
pub recipient_team: TeamName,
pub message_id: AtmMessageId,
pub description: String,
pub requires_ack: bool,
pub is_ack: bool,
pub task_id: Option<TaskId>,
pub recipient_pane_id: Option<PaneId>,
}
pub fn built_in_nudge_template_kind_from_post_send_event(
event: &PostSendHookEvent,
) -> BuiltInNudgeTemplateKind {
match (event.is_ack, event.task_id.is_some(), event.requires_ack) {
(true, true, _) => BuiltInNudgeTemplateKind::AcknowledgeTask,
(true, false, _) => BuiltInNudgeTemplateKind::Acknowledge,
(false, true, true) => BuiltInNudgeTemplateKind::DeliveryTaskAck,
(false, true, false) => BuiltInNudgeTemplateKind::DeliveryTask,
(false, false, true) => BuiltInNudgeTemplateKind::DeliveryAck,
(false, false, false) => BuiltInNudgeTemplateKind::Delivery,
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct ResolvedBuiltInNudgeTemplate {
pub kind: BuiltInNudgeTemplateKind,
pub body: Option<String>,
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum BuiltInNudgeSinkTarget {
Tmux,
Graft,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct InternalNudgeEnvelope {
pub event: PostSendHookEvent,
pub sink_target: BuiltInNudgeSinkTarget,
pub template: ResolvedBuiltInNudgeTemplate,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct LocalTmuxNudgeTarget {
pub pane_id: PaneId,
pub rendered_nudge: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct GraftNudgeTarget {
pub recipient: AgentName,
pub recipient_team: TeamName,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub enum PostSendBuiltInTarget {
LocalTmux(LocalTmuxNudgeTarget),
Graft(GraftNudgeTarget),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct BuiltInPostSendDispatch {
pub event: PostSendHookEvent,
pub target: PostSendBuiltInTarget,
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PostSendEmissionPath {
ExternalHook,
LocalTmux,
GraftPort,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct HookExecutionSummary {
matched_rules: usize,
succeeded_rules: usize,
failed_rules: usize,
}
impl HookExecutionSummary {
pub fn new(
matched_rules: usize,
succeeded_rules: usize,
failed_rules: usize,
) -> Result<Self, AtmError> {
if succeeded_rules + failed_rules > matched_rules {
return Err(AtmError::validation(format!(
"invalid post-send hook execution summary: succeeded ({succeeded_rules}) + failed ({failed_rules}) exceeds matched ({matched_rules})"
))
.with_recovery(
"Count each matching post-send hook rule exactly once before constructing hook execution summary state.",
));
}
Ok(Self {
matched_rules,
succeeded_rules,
failed_rules,
})
}
pub const fn matched_rules(&self) -> usize {
self.matched_rules
}
pub const fn succeeded_rules(&self) -> usize {
self.succeeded_rules
}
pub const fn failed_rules(&self) -> usize {
self.failed_rules
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub enum PostSendEmissionOutcome {
NoCapability {
hook_summary: HookExecutionSummary,
},
Delivered {
path: PostSendEmissionPath,
hook_summary: HookExecutionSummary,
},
Failed {
hook_summary: HookExecutionSummary,
warning: crate::send::WarningEntry,
},
}
pub trait PostSendHookEmitter: sealed::Sealed + Send + Sync {
fn emit_post_send(
&self,
dispatch: &BuiltInPostSendDispatch,
) -> Result<PostSendEmissionPath, AtmError>;
}
pub trait GraftPostSendPort: sealed::Sealed + Send + Sync {
fn deliver_post_send(
&self,
event: &PostSendHookEvent,
target: &GraftNudgeTarget,
) -> Result<(), AtmError>;
}