Skip to main content

cookie_agent_engine/runtime/
approval_api.rs

1use cookie_agent_protocol::{
2    ApprovalListResult, ApprovalRespondParams, ApprovalRespondResult, ApprovalStatus, EventOrigin,
3    SessionId,
4};
5
6use super::{
7    Engine, EngineError, PreparedApprovalInvalidation, SessionCommand,
8    approval_projection::{approval_records, permission_overlay_epoch},
9    helpers::root_id,
10};
11use crate::tool_api::ToolError;
12
13impl Engine {
14    pub async fn approval_respond(
15        &self,
16        params: ApprovalRespondParams,
17        origin: EventOrigin,
18    ) -> Result<ApprovalRespondResult, EngineError> {
19        let _permission_guard = self
20            .inner
21            .approvals
22            .permission_overlay_mutation
23            .lock()
24            .await;
25        let pending = self
26            .inner
27            .approvals
28            .pending
29            .lock()
30            .unwrap_or_else(|poisoned| poisoned.into_inner())
31            .get(&(params.session_id, params.approval_id))
32            .map(|pending| (pending.executor.clone(), pending.permission_overlay_epoch));
33        let Some((executor, pending_epoch)) = pending else {
34            return self
35                .request(params.session_id, |reply| SessionCommand::ApprovalRespond {
36                    params,
37                    origin,
38                    reply,
39                })
40                .await;
41        };
42
43        let guard = executor.lock_owned().await;
44        let mut invalidation = match guard.as_ref() {
45            Some(executor) => executor.revalidate().await.err().map(|error| match error {
46                ToolError::OperationChanged(_) => PreparedApprovalInvalidation::OperationChanged,
47                _ => PreparedApprovalInvalidation::PreparedCapabilityLost,
48            }),
49            None => Some(PreparedApprovalInvalidation::PreparedCapabilityLost),
50        };
51        if invalidation.is_none()
52            && pending_epoch
53                != permission_overlay_epoch(
54                    &self
55                        .inner
56                        .store
57                        .get(params.session_id)?
58                        .log
59                        .event_snapshot(),
60                )
61        {
62            invalidation = Some(PreparedApprovalInvalidation::OperationChanged);
63        }
64        if let Some(invalidation) = invalidation {
65            return self
66                .request(params.session_id, |reply| {
67                    SessionCommand::ApprovalCapabilityInvalid {
68                        params,
69                        invalidation,
70                        reply,
71                    }
72                })
73                .await;
74        }
75        self.request(params.session_id, |reply| SessionCommand::ApprovalRespond {
76            params,
77            origin,
78            reply,
79        })
80        .await
81    }
82
83    #[must_use]
84    pub fn list_approvals(
85        &self,
86        root_session_id: SessionId,
87        status: Option<ApprovalStatus>,
88    ) -> ApprovalListResult {
89        let approvals = self
90            .inner
91            .store
92            .all()
93            .into_iter()
94            .filter(|session| {
95                root_id(&session.meta.origin, session.meta.session_id) == root_session_id
96            })
97            .flat_map(|session| {
98                approval_records(session.meta.session_id, &session.log.event_snapshot())
99                    .into_values()
100            })
101            .filter(|record| status.is_none_or(|status| record.status == status))
102            .collect();
103        ApprovalListResult {
104            approvals,
105            tree_grants: self.inner.approvals.store.for_root(root_session_id),
106        }
107    }
108}