Give loopd's tool port approvals, its own clock and plain denials

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-20 17:43:05 -07:00
parent 3ecaef3c8b
commit 1ceaa36b9b
8 changed files with 600 additions and 55 deletions
+35 -6
View File
@@ -131,14 +131,43 @@ pub fn ok_result(content: &str) -> proto::ToolResponse {
}
}
pub fn pending(approval: u64, expires: &str) -> proto::ToolResponse {
proto::ToolResponse::PendingApproval {
approval,
expires: proto::Timestamp::parse(expires).unwrap(),
}
}
impl loopd::tools::ToolPort for ScriptedPort {
fn call(&self, request: &proto::ToolRequest) -> proto::ToolResponse {
/// A scripted `PendingApproval` is reported through `on_pending`, as the real port does with
/// the broker's pending frame, and the reply after it is the answer. Use
/// `ReturnsPendingPort` for a port that breaks the rule and returns one.
fn call(
&self,
request: &proto::ToolRequest,
on_pending: &mut dyn FnMut(&loopd::tools::Pending),
) -> proto::ToolResponse {
self.calls.lock().unwrap().push(request.clone());
self.replies
.lock()
.unwrap()
.pop_front()
.unwrap_or_else(|| ok_result("scripted"))
let mut replies = self.replies.lock().unwrap();
let mut reply = replies.pop_front();
if let Some(proto::ToolResponse::PendingApproval { approval, expires }) = reply {
on_pending(&loopd::tools::Pending { approval, expires });
reply = replies.pop_front();
}
reply.unwrap_or_else(|| ok_result("scripted"))
}
}
/// A port that breaks the rule: it returns a pending frame as its final answer.
pub struct ReturnsPendingPort;
impl loopd::tools::ToolPort for ReturnsPendingPort {
fn call(
&self,
_request: &proto::ToolRequest,
_on_pending: &mut dyn FnMut(&loopd::tools::Pending),
) -> proto::ToolResponse {
pending(1, "2026-09-18T12:00:00.000Z")
}
}