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
+19 -10
View File
@@ -7,7 +7,7 @@ use crate::config::Config;
use crate::llama::info::{CacheOutcome, cache_outcome};
use crate::llama::{ChatEvent, ChatRequest, Client, InferError};
use crate::session::{Session, SessionError};
use crate::tools::{Registry, ToolPort, cap_result, dispatch};
use crate::tools::{Registry, ToolPort, cap_result, denial_text, dispatch};
use proto::{CallId, DataClass, LogRecord, Timestamp, ToolRequest, ToolResponse, TurnEvent, Usage};
/// What one turn can fail with, beyond the inference server.
@@ -230,7 +230,7 @@ pub fn run_turn(
false,
)
} else {
run_call(rt, session, call, call_id)
run_call(rt, session, call, call_id, on_event)
};
let (text, truncated) = cap_result(&text, rt.cfg.r#loop.tool_result_cap);
@@ -260,6 +260,7 @@ fn run_call(
session: &Session,
call: &proto::ToolCall,
call_id: CallId,
on_event: &mut dyn FnMut(&TurnEvent),
) -> (String, DataClass, bool) {
match dispatch(rt.registry, &call.name, &call.arguments) {
crate::tools::Dispatch::Local(text) => (text, DataClass::Public, false),
@@ -270,7 +271,14 @@ fn run_call(
tool,
arguments,
};
match rt.port.call(&request) {
let response = rt.port.call(&request, &mut |pending| {
on_event(&TurnEvent::ApprovalPending {
approval: pending.approval,
tool: request.tool.clone(),
expires: pending.expires,
});
});
match response {
ToolResponse::Result {
content,
class,
@@ -282,14 +290,15 @@ fn run_call(
DataClass::Public,
false,
),
ToolResponse::Denied { reason } => (
format!("The call was denied: {reason:?}"),
DataClass::Public,
false,
),
ToolResponse::Denied { reason } => {
on_event(&TurnEvent::ToolDenied {
name: request.tool.clone(),
reason,
});
(denial_text(reason).to_string(), DataClass::Public, false)
}
ToolResponse::PendingApproval { .. } => (
"This version of loopd cannot wait for approval; the call was rejected."
.to_string(),
"The tool failed: the tool broker gave no final answer".to_string(),
DataClass::Public,
false,
),