pub(crate) struct ProdSlackFacade {
workspace: Weak<Workspace>,
}Expand description
Production facade over Weak<Workspace> (weak to avoid a cycle with
the MCP server the workspace owns).
Fields§
§workspace: Weak<Workspace>Implementations§
Source§impl ProdSlackFacade
impl ProdSlackFacade
Sourceconst THREAD_EXCERPT_CHARS: usize = 80
const THREAD_EXCERPT_CHARS: usize = 80
How much of a thread’s parent the card’s line quotes.
pub(crate) fn from_arc(workspace: &Arc<Workspace>) -> Arc<dyn SlackFacade> ⓘ
Sourcefn resolve(
&self,
caller: &SessionSlot,
) -> Result<(String, Arc<dyn SlackApi>), SlackWorkspaceError>
fn resolve( &self, caller: &SessionSlot, ) -> Result<(String, Arc<dyn SlackApi>), SlackWorkspaceError>
The workspace a tool call acts on, with the refusal shape every Slack tool shares.
Sourceasync fn resolve_conversation(
api: &Arc<dyn SlackApi>,
workspace: &str,
conversation: &str,
) -> (String, String)
async fn resolve_conversation( api: &Arc<dyn SlackApi>, workspace: &str, conversation: &str, ) -> (String, String)
The conversation a request addresses, as (id, display label).
conversation may be an id, a channel name or a DM’s partner, and
the id is what the ledger and the in-flight mark key on - a post or
an edit addressed one way lands in the same conversation the sweep
reads. The label is what the approval prompt shows: a channel’s name
carries the # the card draws, a DM is named after its partner - the
partner’s id when Slack names none. A failed lookup is not a reason to
refuse the action - but it IS why the prompt would show an id, so it
says so.
Sourceasync fn name_draft(
ws: &Arc<Workspace>,
api: &Arc<dyn SlackApi>,
label: &str,
conversation: &str,
draft: &mut SlackDraft,
)
async fn name_draft( ws: &Arc<Workspace>, api: &Arc<dyn SlackApi>, label: &str, conversation: &str, draft: &mut SlackDraft, )
Name the draft’s thread and the people its text tags, at the moment it is parked. Never fails the park: whatever resolves rides the draft, and what does not stays unset.
Sourcefn look_up_user(
host: &Arc<SlackSubsystemHost>,
api: &Arc<dyn SlackApi>,
label: &str,
user: String,
) -> JoinHandle<Option<String>> ⓘ
fn look_up_user( host: &Arc<SlackSubsystemHost>, api: &Arc<dyn SlackApi>, label: &str, user: String, ) -> JoinHandle<Option<String>> ⓘ
One user-name lookup on its own task, so a batch of them overlaps the thread read in front of the park.
Sourcefn thread_name(
parent: &SlackHistoryMessage,
names: &HashMap<String, String>,
) -> Option<SlackThreadName>
fn thread_name( parent: &SlackHistoryMessage, names: &HashMap<String, String>, ) -> Option<SlackThreadName>
The thread name drawn for parent: its author and one line of its
text flattened to prose. None when the parent names no author at all.
Sourceasync fn await_approval(
workspace: &Arc<Workspace>,
caller: &SessionSlot,
draft: SlackDraft,
) -> GateDecision
async fn await_approval( workspace: &Arc<Workspace>, caller: &SessionSlot, draft: SlackDraft, ) -> GateDecision
Register a draft for the user’s decision and wait for it. The one place “did the user approve this” is decided, so the fail-closed path exists once rather than at each gated operation - posting, replacing a body and deleting one all come through here.
No timeout rides the wait. A held post is an ask like a question, a permission or a hand-off: it parks until the reader answers or the asking session goes away, and the dock’s own “don’t send” is the way out. A clock here would refuse a draft the user was about to approve.
The views hear SlackDraftResolved from the answering dispatch: the
draft leaving the registry is the whole of what a view does with it,
and every path that removes it emits that one signal.
Trait Implementations§
Source§impl SlackFacade for ProdSlackFacade
impl SlackFacade for ProdSlackFacade
Source§fn conversations<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackConversation>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn conversations<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackConversation>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn post<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
request: SlackPostRequest,
) -> Pin<Box<dyn Future<Output = Result<SlackPostOutcome, SlackPostError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn post<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
request: SlackPostRequest,
) -> Pin<Box<dyn Future<Output = Result<SlackPostOutcome, SlackPostError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Rejected.Source§fn edit<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
request: SlackEditRequest,
) -> Pin<Box<dyn Future<Output = Result<(), SlackEditError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn edit<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
request: SlackEditRequest,
) -> Pin<Box<dyn Future<Output = Result<(), SlackEditError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn react<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
request: SlackReactRequest,
) -> Pin<Box<dyn Future<Output = Result<(), SlackReactError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn react<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
request: SlackReactRequest,
) -> Pin<Box<dyn Future<Output = Result<(), SlackReactError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Source§fn search<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
query: &'life2 str,
count: u32,
conversation: Option<&'life3 str>,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackSearchMatch>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn search<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
query: &'life2 str,
count: u32,
conversation: Option<&'life3 str>,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackSearchMatch>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
search.messages in a workspace, always timestamp-ordered. It
matches text; it cannot find every message that mentions a user.
conversation keeps only hits from that conversation id.Source§fn history<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
conversation: &'life2 str,
limit: u32,
oldest: Option<&'life3 str>,
newest: Option<&'life4 str>,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackHistoryMessage>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
fn history<'life0, 'life1, 'life2, 'life3, 'life4, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
conversation: &'life2 str,
limit: u32,
oldest: Option<&'life3 str>,
newest: Option<&'life4 str>,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackHistoryMessage>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
'life4: 'async_trait,
oldest and newest are
exclusive bounds, so a caller pages backwards by feeding the earliest
ts it saw back as newest.Source§fn replies<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
conversation: &'life2 str,
ts: &'life3 str,
limit: u32,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackHistoryMessage>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn replies<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
conversation: &'life2 str,
ts: &'life3 str,
limit: u32,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackHistoryMessage>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
limit rows.Source§fn user<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
user: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<(String, SlackUser), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn user<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
user: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<(String, SlackUser), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
users.info for one user id.Source§fn users<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
query: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackUser>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn users<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
query: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackUser>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
query, case-insensitively. Every match is returned: silently
picking one of several is how a mention reaches the wrong person.
Deactivated members are excluded.Source§fn me<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
) -> Pin<Box<dyn Future<Output = Result<(String, AuthTest), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn me<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
) -> Pin<Box<dyn Future<Output = Result<(String, AuthTest), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
auth.test: the resolved [[slack]] label and the identity the
workspace’s token acts as. The label is returned alongside because it
can differ from the team’s name (Subspace is team Autonomys).Source§fn pins<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
conversation: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackPin>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn pins<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
conversation: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackPin>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
pins.list for one conversation.Source§fn bookmarks<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
conversation: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackBookmark>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
fn bookmarks<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
conversation: &'life2 str,
) -> Pin<Box<dyn Future<Output = Result<(String, Vec<SlackBookmark>), SlackReadError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
bookmarks.list for one conversation.Source§fn fetch_attachment<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
request: SlackFetchRequest,
) -> Pin<Box<dyn Future<Output = Result<PathBuf, SlackAttachmentError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn fetch_attachment<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
request: SlackFetchRequest,
) -> Pin<Box<dyn Future<Output = Result<PathBuf, SlackAttachmentError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
request.dir and return where it landed.
Not gated: reading a file is not posting one, and the name is
sanitised so it cannot escape the directory.Source§fn post_attachment<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
request: SlackUploadRequest,
) -> Pin<Box<dyn Future<Output = Result<(), SlackAttachmentError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn post_attachment<'life0, 'life1, 'async_trait>(
&'life0 self,
caller: &'life1 SessionSlot,
request: SlackUploadRequest,
) -> Pin<Box<dyn Future<Output = Result<(), SlackAttachmentError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
slack__post.Source§fn subscribed_targets(
&self,
caller: &SessionSlot,
label: &str,
) -> Vec<SlackSubscription>
fn subscribed_targets( &self, caller: &SessionSlot, label: &str, ) -> Vec<SlackSubscription>
label - a lead’s the lead’s, a
worker’s its own, never another owner’s. slack__list passes the
label its read resolved, so the mark and the rows cannot disagree
about which workspace they are in. After a restart these ids are
the only handle a session has to unsubscribe with.Source§fn subscribe(
&self,
caller: &SessionSlot,
request: SlackSubscribeRequest,
) -> Result<Vec<SlackSubscription>, SlackSubscribeError>
fn subscribe( &self, caller: &SessionSlot, request: SlackSubscribeRequest, ) -> Result<Vec<SlackSubscription>, SlackSubscribeError>
Source§fn unsubscribe(
&self,
caller: &SessionSlot,
id: Uuid,
) -> Result<SlackSubscription, SlackUnsubscribeError>
fn unsubscribe( &self, caller: &SessionSlot, id: Uuid, ) -> Result<SlackSubscription, SlackUnsubscribeError>
SlackUnsubscribeError::NoSuchSubscription when the
project has no such id, SlackUnsubscribeError::NotOwnedByCaller
when it has one under another owner.Auto Trait Implementations§
impl !RefUnwindSafe for ProdSlackFacade
impl !UnwindSafe for ProdSlackFacade
impl Freeze for ProdSlackFacade
impl Send for ProdSlackFacade
impl Sync for ProdSlackFacade
impl Unpin for ProdSlackFacade
impl UnsafeUnpin for ProdSlackFacade
Blanket Implementations§
§impl<T> AnySync for T
impl<T> AnySync for T
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
impl<S, T> Duplex<S> for Twhere
T: FromSample<S> + ToSample<S>,
§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more