feat: reset bot session context while retaining monitoring history

This commit is contained in:
Hyu
2026-10-10 22:35:27 +08:00
parent b8a7649ac2
commit 1c70218383
20 changed files with 408 additions and 5 deletions
@@ -545,6 +545,24 @@ class MonitoringRouterGroup(group.RouterGroup):
}
)
@self.route('/sessions/reset-context', methods=['POST'],
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY, permission=Permission.RESOURCE_MANAGE)
async def reset_session_context(request_context: RequestContext):
body = await quart.request.get_json()
if not isinstance(body, dict):
return self.http_status(400, 'invalid_request', 'Expected a JSON object')
try:
result = await self.ap.monitoring_service.reset_session_context(
request_context, body.get('bot_id'), body.get('session_id'),
)
except ValueError as exc:
return self.http_status(400, 'invalid_request', str(exc))
except LookupError as exc:
return self.http_status(404, 'resource_not_found', str(exc))
except RuntimeError as exc:
return self.http_status(409, 'session_busy', str(exc))
return self.success(data=result)
@self.route(
'/sessions/<session_id>/analysis',
methods=['GET'],
@@ -2193,6 +2193,60 @@ class MonitoringService(ExecutionDetailsMixin):
total,
)
@_workspace_transaction
async def reset_session_context(self, context: TenantContext, bot_id: str, session_id: str) -> dict:
"""Reset model context without deleting monitoring history or other sessions."""
from ....agent.runner.context_reset import HOST_RESET_RUNNER, reset_scope
from ....entity.persistence.runner_state import RunnerState
if not isinstance(bot_id, str) or not bot_id or not isinstance(session_id, str) or not session_id:
raise ValueError('bot_id and session_id are required')
workspace = require_workspace_uuid(context)
execute = self.ap.persistence_mgr.execute_async
source = self._session_message_source(workspace, [bot_id])
exists = (await execute(sqlalchemy.select(source.c.session_id).where(
source.c.session_id == session_id,
).limit(1))).first()
if exists is None:
session = persistence_monitoring.MonitoringSession
exists = (await execute(sqlalchemy.select(session.session_id).where(
session.workspace_uuid == workspace, session.bot_id == bot_id, session.session_id == session_id,
).limit(1))).first()
if exists is None:
raise LookupError('Session not found')
# Legacy sessions use uppercase PERSON/GROUP; envelope IDs use lowercase.
conversation = re.sub(r'^(PERSON|GROUP)_', lambda match: match[1].lower() + '_', session_id)
run = persistence_agent_run.AgentRun
busy = (await execute(sqlalchemy.select(run.run_id).where(
run.workspace_id == workspace, run.bot_id == bot_id, run.conversation_id == conversation,
run.status.in_(['created', 'queued', 'claimed', 'running']),
).limit(1))).first()
message = persistence_monitoring.MonitoringMessage
pending = (await execute(sqlalchemy.select(message.id).where(
message.workspace_uuid == workspace, message.bot_id == bot_id,
message.session_id.in_([session_id, conversation]), message.status == 'pending',
).limit(1))).first()
interaction = persistence_agent_interaction.AgentInteraction
waiting = (await execute(sqlalchemy.select(interaction.id).where(
interaction.workspace_id == workspace, interaction.bot_id == bot_id,
interaction.conversation_id == conversation, interaction.status.in_(['pending', 'submitted']),
sqlalchemy.or_(interaction.expires_at.is_(None), interaction.expires_at > datetime.datetime.utcnow()),
).limit(1))).first()
if busy or pending or waiting:
raise RuntimeError('Session is processing a task; wait until it finishes')
await execute(sqlalchemy.delete(RunnerState).where(
RunnerState.workspace_id == workspace, RunnerState.bot_id == bot_id,
RunnerState.conversation_id == conversation, RunnerState.scope == 'conversation',
))
generation = str(uuid.uuid4())
await execute(sqlalchemy.insert(RunnerState).values(
runner_id=HOST_RESET_RUNNER, binding_identity=HOST_RESET_RUNNER, scope='conversation',
scope_key=reset_scope(workspace, bot_id, conversation), state_key='generation',
value_json=json.dumps(generation), workspace_id=workspace, bot_id=bot_id,
conversation_id=conversation, created_at=datetime.datetime.utcnow(),
))
return {'reset': True, 'session_id': session_id}
async def get_sessions(
self,
context: TenantContext,
+6
View File
@@ -211,6 +211,12 @@ class LangBotMCPServer:
async def get_inflight_executions() -> str:
return _dump(await ap.monitoring_service.get_inflight_snapshot(_authorized(Permission.RESOURCE_VIEW)))
@mcp.tool(description='Reset a bot session model context, preserving monitoring history. Refuses busy sessions.')
async def reset_session_context(bot_id: str, session_id: str) -> str:
return _dump(await ap.monitoring_service.reset_session_context(
_authorized(Permission.RESOURCE_MANAGE), bot_id, session_id,
))
@mcp.tool(
description='List Workspace executions, including events that did not start a processor. The legacy pipeline_ids filter accepts Agent, Pipeline and event processor IDs. Returns bounded rows and summary metrics.'
)