fix(sessions): resolve blockers and warnings in handle_session_approve
Neuron Soul CI / build (pull_request) Failing after 9m3s
Neuron Soul CI / build (pull_request) Failing after 9m3s
BLOCKER 1 (sessions.el, modern path): Add guard that rejects allow
action when tool_name is missing from the body. Previously, omitting
tool_name caused dispatch_tool("", ...) to return "unknown tool: " and
silently inject a corrupted tool_result into the conversation.
BLOCKER 2 (sessions.el, modern path): Stop re-executing client-side
tools server-side. When the client provides body["content"], use it
directly as the tool result (matching the handle_tool_result contract).
Only fall back to dispatch_tool for builtin tools when no content is
present. Non-builtin tools with no client content now return a clear
error instead of a broken dispatch attempt.
WARNING 1 (chat.el, agentic_loop): Wire always_allow_<session_id> state
into the bridge-suspension decision. When a tool is in the session's
always-allow list, treat it as locally dispatchable (like a builtin)
and skip the bridge pause, so the approval UI is never shown again for
that tool in that session.
WARNING 2 (sessions.el, legacy path): Read a "tools_variant" field from
the legacy pending blob when present, and call the corresponding
agentic_tools_*() variant on resume. Falls back to agentic_tools_literal()
for blobs written before this field existed.
tests/test_sessions_approve.el: Add 10-case test suite covering:
- empty session_id / missing call_id / missing action guards
- no pending tool returns correct error
- missing tool_name on allow returns error (BLOCKER 1)
- deny action does not require tool_name
- legacy call_id mismatch returns mismatch error
- always action records tool_name in always_allow state
- allow with client content skips re-execution (BLOCKER 2)
This commit is contained in:
@@ -691,7 +691,13 @@ fn agentic_loop(session_id: String, model: String, safe_sys: String, tools_json:
|
||||
// A real tool turn that targets a tool the soul cannot run in-process is a
|
||||
// CLIENT bridge: suspend the loop and hand the tool to the client.
|
||||
let is_tool_turn: Bool = str_eq(stop_reason, "tool_use") && has_tool
|
||||
let needs_bridge: Bool = is_tool_turn && !is_builtin_tool(tool_name)
|
||||
// If the user previously chose "always allow" for this tool in this session,
|
||||
// treat it like a builtin — run server-side via dispatch_tool and skip the
|
||||
// bridge suspension entirely so the approval UI is never shown again.
|
||||
let always_key: String = "always_allow_" + session_id
|
||||
let always_list: String = if !str_eq(session_id, "") { state_get(always_key) } else { "" }
|
||||
let is_always_allowed: Bool = !str_eq(tool_name, "") && !str_eq(always_list, "") && str_contains(always_list, tool_name)
|
||||
let needs_bridge: Bool = is_tool_turn && !is_builtin_tool(tool_name) && !is_always_allowed
|
||||
|
||||
// Built-in tools dispatch locally; bridged tools yield "" (never sent upstream).
|
||||
let tool_result_raw: String = if is_tool_turn && !needs_bridge { dispatch_tool(tool_name, tool_input) } else { "" }
|
||||
|
||||
Reference in New Issue
Block a user