mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-08 05:18:49 +03:00
refactor(session): remove request-scoped access grants (#5238)
This commit is contained in:
@@ -12,7 +12,6 @@ from nanobot.agent.tools.context import RequestContext, request_context
|
||||
from nanobot.agent.tools.loader import ToolLoader
|
||||
from nanobot.agent.tools.registry import ToolRegistry
|
||||
from nanobot.agent.tools.sessions import ReadSessionTool, SearchSessionsTool
|
||||
from nanobot.bus.events import INBOUND_META_SESSION_READ_SCOPE
|
||||
from nanobot.runtime_context import RuntimeContextBlock, append_runtime_context
|
||||
from nanobot.session.manager import SessionManager
|
||||
from nanobot.webui.transcript import append_transcript_object
|
||||
@@ -46,7 +45,6 @@ def _webui_request(
|
||||
channel="websocket",
|
||||
chat_id=session_key.removeprefix("websocket:"),
|
||||
session_key=session_key,
|
||||
metadata={INBOUND_META_SESSION_READ_SCOPE: "websocket:"},
|
||||
))
|
||||
|
||||
|
||||
@@ -56,22 +54,29 @@ def test_session_tools_are_discovered() -> None:
|
||||
assert {"ReadSessionTool", "SearchSessionsTool"} <= names
|
||||
|
||||
|
||||
def test_session_tools_are_visible_only_in_an_authorized_request(tmp_path) -> None:
|
||||
def test_session_tools_stay_visible_when_enabled(tmp_path) -> None:
|
||||
manager = SessionManager(tmp_path)
|
||||
registry = ToolRegistry()
|
||||
registry.register(SearchSessionsTool(manager))
|
||||
registry.register(ReadSessionTool(manager))
|
||||
|
||||
assert registry.get_definitions() == []
|
||||
with _webui_request():
|
||||
names = {
|
||||
definition["function"]["name"]
|
||||
for definition in registry.get_definitions()
|
||||
}
|
||||
names = {
|
||||
definition["function"]["name"]
|
||||
for definition in registry.get_definitions()
|
||||
}
|
||||
|
||||
assert names == {"read_session", "search_sessions"}
|
||||
|
||||
|
||||
def test_session_tools_do_not_own_runtime_context(tmp_path) -> None:
|
||||
manager = SessionManager(tmp_path)
|
||||
registry = ToolRegistry()
|
||||
registry.register(SearchSessionsTool(manager))
|
||||
registry.register(ReadSessionTool(manager))
|
||||
|
||||
assert registry.get_runtime_context_providers() == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_sessions_reads_the_full_webui_transcript_after_compaction(
|
||||
tmp_path,
|
||||
@@ -242,7 +247,7 @@ async def test_read_session_reports_invalid_requests(tmp_path):
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_session_tools_reject_unscoped_and_out_of_scope_sessions(tmp_path):
|
||||
async def test_session_tools_read_persisted_sessions_from_any_channel(tmp_path):
|
||||
manager = SessionManager(tmp_path)
|
||||
_save_session(
|
||||
manager,
|
||||
@@ -252,8 +257,14 @@ async def test_session_tools_reject_unscoped_and_out_of_scope_sessions(tmp_path)
|
||||
)
|
||||
_save_session(
|
||||
manager,
|
||||
"slack:private",
|
||||
title="Private",
|
||||
"slack:history",
|
||||
title="Slack history",
|
||||
messages=[{"role": "user", "content": "needle"}],
|
||||
)
|
||||
_save_session(
|
||||
manager,
|
||||
"telegram:external",
|
||||
title="Current",
|
||||
messages=[{"role": "user", "content": "needle"}],
|
||||
)
|
||||
tools = SearchSessionsTool(manager), ReadSessionTool(manager)
|
||||
@@ -263,31 +274,22 @@ async def test_session_tools_reject_unscoped_and_out_of_scope_sessions(tmp_path)
|
||||
chat_id="external",
|
||||
session_key="telegram:external",
|
||||
)):
|
||||
search = await tools[0].execute(query="needle")
|
||||
read = await tools[1].execute(session_key="websocket:visible")
|
||||
|
||||
assert search.is_error
|
||||
assert read.is_error
|
||||
|
||||
with request_context(RequestContext(
|
||||
channel="websocket",
|
||||
chat_id="spoofed",
|
||||
session_key="websocket:spoofed",
|
||||
metadata={"webui": True},
|
||||
)):
|
||||
spoofed = await tools[0].execute(query="needle")
|
||||
|
||||
with _webui_request():
|
||||
search = _decode(await tools[0].execute(query="needle"))
|
||||
read = await tools[1].execute(session_key="slack:private")
|
||||
websocket_read = _decode(await tools[1].execute(session_key="websocket:visible"))
|
||||
slack_read = _decode(await tools[1].execute(session_key="slack:history"))
|
||||
current_read = await tools[1].execute(session_key="telegram:external")
|
||||
|
||||
assert spoofed.is_error
|
||||
assert [row["session_key"] for row in search["results"]] == ["websocket:visible"]
|
||||
assert read.is_error
|
||||
assert {row["session_key"] for row in search["results"]} == {
|
||||
"websocket:visible",
|
||||
"slack:history",
|
||||
}
|
||||
assert websocket_read["session_key"] == "websocket:visible"
|
||||
assert slack_read["session_key"] == "slack:history"
|
||||
assert current_read.is_error and "session not found" in str(current_read)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_session_tools_use_the_scope_granted_by_the_channel(tmp_path):
|
||||
async def test_session_tools_work_without_request_context(tmp_path):
|
||||
manager = SessionManager(tmp_path)
|
||||
_save_session(
|
||||
manager,
|
||||
@@ -296,12 +298,8 @@ async def test_session_tools_use_the_scope_granted_by_the_channel(tmp_path):
|
||||
messages=[{"role": "user", "content": "custom needle"}],
|
||||
)
|
||||
|
||||
with request_context(RequestContext(
|
||||
channel="custom",
|
||||
chat_id="current",
|
||||
session_key="custom:current",
|
||||
metadata={INBOUND_META_SESSION_READ_SCOPE: "custom:"},
|
||||
)):
|
||||
result = _decode(await SearchSessionsTool(manager).execute(query="needle"))
|
||||
result = _decode(await SearchSessionsTool(manager).execute(query="needle"))
|
||||
read = _decode(await ReadSessionTool(manager).execute(session_key="custom:history"))
|
||||
|
||||
assert [row["session_key"] for row in result["results"]] == ["custom:history"]
|
||||
assert read["session_key"] == "custom:history"
|
||||
|
||||
Reference in New Issue
Block a user