fix: one tool-set recipe per session, so a tool cannot vanish between rounds
Nightly Build / build (push) Successful in 7m36s
Nightly Build / build (push) Successful in 7m36s
Two "unknown tool (not in this turn's tool set)" failures, one disease: the turn's tool set was rebuilt from a different recipe depending on which entry point happened to drive it. A sub-agent got `ask_user_clarification`, `execute_subtask` and `activate_tools` and nothing else — while `agents/common/tools.md` and every reporting agent's prompt tell it to register its output with `update_scratchpad`. The child could see the scratchpad injected into its context but had no way to write to it. It now gets the scratchpad and todos tools, on the parent's `scratchpad_sid`: one blackboard per session, as the surrounding code already declared. `show_file_to_user` was injected per message by the WS handler, while `resume_session` and `resolve_pending_call` rebuilt the list with `execute_task` alone. So approving a card, or reconnecting mid-turn, continued the *same* conversation with the tool silently gone. There is now a single recipe, `ChatHub::session_interface_tools`, used by all three paths and fed by a builder the shell installs once through `Skald::set_interface_tools_builder`: the core keeps owning the tool, the shell keeps owning the policy of who gets it — Telegram still does not, since it cannot act on OpenFile.
This commit is contained in:
+5
-12
@@ -404,18 +404,11 @@ async fn handle_socket(mut socket: WebSocket, skald: Arc<Skald>, source: String,
|
||||
// set_selected_client, which broadcasts ClientSelected.
|
||||
client_name: chat_hub.get_selected_client(&source).await,
|
||||
extra_system_context: Some(WEB_FORMAT_CONTEXT.to_string()),
|
||||
// SPA-only tool: lets the assistant open a file in the user's
|
||||
// viewer. Injected here (not in the registry) so it exists only
|
||||
// for ws.rs clients (web + mobile), never for the Telegram plugin.
|
||||
interface_tools: vec![
|
||||
skald_core::tools::show_file::make_tool(
|
||||
Arc::clone(&chat_hub),
|
||||
source.clone(),
|
||||
ctx.fs.clone(),
|
||||
ctx.pool.as_ref().clone(),
|
||||
skald.db().as_ref().clone(),
|
||||
),
|
||||
],
|
||||
// `show_file_to_user` used to be injected right here, per
|
||||
// message — which is why it disappeared from a conversation
|
||||
// the moment an approval or a reconnect resumed the turn
|
||||
// through another path. It is now declared once, for every
|
||||
// path, by `WebFrontend::interface_tools_builder`.
|
||||
..Default::default()
|
||||
};
|
||||
// send_message only enqueues — the turn runs on ChatHub's per-source
|
||||
|
||||
@@ -10,6 +10,7 @@ use tracing::{error, info};
|
||||
|
||||
use core_api::plugin::RouterFactory;
|
||||
use crate::frontend::config::FrontendConfig;
|
||||
use skald_core::chat_hub::InterfaceToolsBuilder;
|
||||
use skald_core::skald::Skald;
|
||||
use crate::frontend::server::{WebServer, WebServerHandle};
|
||||
|
||||
@@ -43,7 +44,35 @@ impl WebFrontend {
|
||||
})
|
||||
}
|
||||
|
||||
/// The tools this surface contributes to a chat session, whichever entry
|
||||
/// point drives the turn (a live message, a reconnect, an approval answered
|
||||
/// from the Inbox).
|
||||
///
|
||||
/// `show_file_to_user` opens a file in the user's viewer by emitting
|
||||
/// `ServerEvent::OpenFile` — only a WS client that renders the viewer can
|
||||
/// act on it. The Telegram plugin ignores that event and has
|
||||
/// `send_attachment` instead, so a Telegram conversation must not be
|
||||
/// offered the tool: the model would report having shown a file nobody saw.
|
||||
fn interface_tools_builder() -> InterfaceToolsBuilder {
|
||||
Arc::new(|hub, source, handler| {
|
||||
if source == plugin_telegram_bot::SOURCE {
|
||||
return Vec::new();
|
||||
}
|
||||
vec![skald_core::tools::show_file::make_tool(
|
||||
hub,
|
||||
source.to_string(),
|
||||
handler.shared_fs(),
|
||||
handler.owner_pool().as_ref().clone(),
|
||||
handler.shared_pool().as_ref().clone(),
|
||||
)]
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn start(self) -> Result<WebServerHandle> {
|
||||
// What the SPA lends to a session's tool set. Installed before anything
|
||||
// can serve a request, so every per-user hub built at login gets it.
|
||||
self.skald.set_interface_tools_builder(Self::interface_tools_builder());
|
||||
|
||||
// Provide the router factory and web port to plugins before start_enabled().
|
||||
self.skald.plugin_manager().set_router_factory(self.make_router_factory());
|
||||
self.skald.plugin_manager().set_web_port(self.port);
|
||||
|
||||
Reference in New Issue
Block a user