mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
A pre_llm_call veto kept the typed text as the user row's content and put the placeholder only in api_content, which only the main-loop wire replay reads. Compaction (summary request and handoff), session_search, external memory providers and the routed background-review digest all read content, so the vetoed prompt reached a model at the next compaction or search. - The placeholder is now the row's content and the persist override; the typed text lives only in display_metadata.withheld_prompt, which the CLI resume recap, TUI/Desktop session history and the dashboard render. - A row the TUI/Desktop wrote at submit time is rewritten in place (set_user_message_content gains an optional display_metadata, also on the managed-worker store op). - The hook now runs before turn-start compaction and a blocked turn skips it: that summary request quotes recent user turns, this one included. - Memory-provider transcript hand-offs drop the display-only key.
94 lines
4.7 KiB
Python
94 lines
4.7 KiB
Python
"""Explicit lifecycle methods for the assignment-bound worker store.
|
|
|
|
The owner reserves identity before construction. Closing the local store remains
|
|
separate from ending its session and settling execution (including deferred work).
|
|
"""
|
|
|
|
|
|
class RuntimeSessionLifecycleMixin:
|
|
def create_session(self, session_id, source, *, model=None, model_config=None,
|
|
system_prompt=None, user_id=None, session_key=None, chat_id=None,
|
|
chat_type=None, thread_id=None, parent_session_id=None, cwd=None,
|
|
profile_name=None, git_repo_root=None, origin_json=None, display_name=None):
|
|
self._session(session_id)
|
|
payload = dict(source=source, model=model, model_config=model_config, system_prompt=system_prompt,
|
|
user_id=user_id, session_key=session_key, chat_id=chat_id, chat_type=chat_type,
|
|
thread_id=thread_id, parent_session_id=parent_session_id, cwd=cwd,
|
|
profile_name=profile_name, git_repo_root=git_repo_root,
|
|
origin_json=origin_json, display_name=display_name)
|
|
return self._apply('session.create', payload)['value']
|
|
|
|
def set_session_title(self, session_id, title):
|
|
self._session(session_id)
|
|
return self._title_result(self._apply('session.title', {'title': title, 'source': 'user'}))
|
|
|
|
def set_auto_title(self, session_id, title, *, source):
|
|
if source not in ('derived', 'llm'):
|
|
raise ValueError(f'invalid automatic title source: {source!r}')
|
|
self._session(session_id)
|
|
return self._title_result(self._apply('session.title', {'title': title, 'source': source}))
|
|
|
|
@staticmethod
|
|
def _title_result(result):
|
|
if result.get('error'):
|
|
raise ValueError(result['error'])
|
|
return result['value']
|
|
|
|
def get_session_title_source(self, session_id):
|
|
row = self.get_session(session_id)
|
|
return row.get('title_source') if row.get('title') is not None else None
|
|
|
|
def set_session_title_source(self, session_id, source):
|
|
if source not in ('user', 'derived', 'llm'):
|
|
raise ValueError(f'invalid title source: {source!r}')
|
|
self._session(session_id)
|
|
return self._apply('session.title_source', {'source': source})['value']
|
|
|
|
def get_next_title_in_lineage(self, base_title):
|
|
return self._apply('session.next_title', {'base_title': base_title})['value']
|
|
|
|
def touch_session_activity(self, session_id, ts=None, *, description=None, provenance=None):
|
|
self._session(session_id)
|
|
self._apply('session.activity', {'ts': ts, 'description': description, 'provenance': provenance})
|
|
|
|
def clear_session_activity_labels(self, session_id):
|
|
self._session(session_id)
|
|
self._apply('session.activity_clear', {})
|
|
|
|
def update_session_billing_route(self, session_id, *, provider, base_url, billing_mode=None):
|
|
self._session(session_id)
|
|
# Every preceding usage call already owns a receipt. _apply refuses a
|
|
# pending or failed delta, so a route switch cannot overtake accounting.
|
|
self._apply('session.billing_route', {'provider': provider, 'base_url': base_url,
|
|
'billing_mode': billing_mode})
|
|
|
|
def set_latest_user_api_content(self, session_id, content, api_content):
|
|
self._session(session_id)
|
|
return self._apply('session.api_content', {'content': content, 'api_content': api_content})['value']
|
|
|
|
# The turn prologue's row-addressed backfills (agent/turn_context.py) for a user row another
|
|
# writer materialized first (in-place preflight compaction, an early flush, a submit-time row).
|
|
def set_message_api_content(self, session_id, row_id, content, api_content):
|
|
self._session(session_id)
|
|
return self._apply('session.message_api_content',
|
|
{'row_id': row_id, 'content': content, 'api_content': api_content})['value']
|
|
|
|
def set_user_message_content(self, session_id, row_id, content, display_metadata=None):
|
|
self._session(session_id)
|
|
payload = {'row_id': row_id, 'content': content}
|
|
if display_metadata is not None:
|
|
payload['display_metadata'] = display_metadata
|
|
return self._apply('session.user_content', payload)['value']
|
|
|
|
def end_session(self, session_id, end_reason):
|
|
self._session(session_id)
|
|
self._apply('session.end', {'end_reason': end_reason})
|
|
|
|
def session_lifecycle_statuses(self, session_ids):
|
|
ids = [sid for sid in (session_ids or []) if sid]
|
|
for sid in ids:
|
|
self._session(sid)
|
|
if not ids:
|
|
return {}
|
|
return {self.scope['session_id']: self._apply('session.lifecycle', {})['value']}
|