cookie_agent_engine/runtime/
approval_api.rs1use 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}