|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
import base64
|
|
import base64
|
|
|
import hmac
|
|
import hmac
|
|
|
import hashlib
|
|
import hashlib
|
|
|
|
|
+import importlib.util
|
|
|
import json
|
|
import json
|
|
|
import math
|
|
import math
|
|
|
import mimetypes
|
|
import mimetypes
|
|
@@ -29,7 +30,7 @@ from pathlib import Path
|
|
|
from typing import Any
|
|
from typing import Any
|
|
|
|
|
|
|
|
VERSION = 1
|
|
VERSION = 1
|
|
|
-PLUGIN_VERSION = "1.3.1"
|
|
|
|
|
|
|
+PLUGIN_VERSION = "1.4.0"
|
|
|
def _plugin_fingerprint():
|
|
def _plugin_fingerprint():
|
|
|
root = Path(__file__).parent
|
|
root = Path(__file__).parent
|
|
|
digest = hashlib.sha256()
|
|
digest = hashlib.sha256()
|
|
@@ -105,6 +106,59 @@ def register_context(ctx):
|
|
|
ctx.register_hook("pre_tool_call", computer_directive)
|
|
ctx.register_hook("pre_tool_call", computer_directive)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _bot_context(agent, home, stored):
|
|
|
|
|
+ module_name = '_yaoyao_bot_context_projection_v1'
|
|
|
|
|
+ module = sys.modules.get(module_name)
|
|
|
|
|
+ if module is None:
|
|
|
|
|
+ spec = importlib.util.spec_from_file_location(module_name, Path(__file__).with_name('context_projection.py'))
|
|
|
|
|
+ module = importlib.util.module_from_spec(spec)
|
|
|
|
|
+ spec.loader.exec_module(module)
|
|
|
|
|
+ sys.modules[module_name] = module
|
|
|
|
|
+
|
|
|
|
|
+ def active():
|
|
|
|
|
+ try:
|
|
|
|
|
+ return _resolve(getattr(agent, 'session_id', '') or stored).agent is agent
|
|
|
|
|
+ except BridgeError:
|
|
|
|
|
+ return False
|
|
|
|
|
+ return module.install(agent, home, stored, active)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def prepare_context(owner, body):
|
|
|
|
|
+ # Preparation happens BEFORE prompt.submit. Tool dispatch's _resolve()
|
|
|
|
|
+ # deliberately requires a running turn, so validate the idle grant directly.
|
|
|
|
|
+ server = _server()
|
|
|
|
|
+ sid, generation = _string(body, 'session_id'), _string(body, 'generation', 128)
|
|
|
|
|
+ with server._sessions_lock, _lock:
|
|
|
|
|
+ _purge(server)
|
|
|
|
|
+ binding, session = _bindings.get(sid), server._sessions.get(sid)
|
|
|
|
|
+ if binding is None or session is not binding.session or session.get('agent') is not binding.agent:
|
|
|
|
|
+ raise BridgeError('此轮上下文授权已失效')
|
|
|
|
|
+ _check_session_mode(session)
|
|
|
|
|
+ if (binding.owner != owner or binding.generation != generation or session.get('_finalized')
|
|
|
|
|
+ or _transport_identity(session.get('transport')) != owner
|
|
|
|
|
+ or _profile_home(session, binding.profile) != binding.profile_home):
|
|
|
|
|
+ raise BridgeError('当前登录不能修改此轮上下文')
|
|
|
|
|
+ if session.get('running'):
|
|
|
|
|
+ raise BridgeError('会话正在运行,不能修改本轮上下文', 409, 'session_busy')
|
|
|
|
|
+ try:
|
|
|
|
|
+ context = _bot_context(binding.agent, binding.profile_home, binding.stored_session_id)
|
|
|
|
|
+ context.register(body.get('prompt'), body.get('history_text'))
|
|
|
|
|
+ except ValueError as exc:
|
|
|
|
|
+ raise BridgeError(str(exc), 400, 'invalid_context') from None
|
|
|
|
|
+ return {'ok': True, 'context_projection_version': 1}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def read_context(args, **kwargs):
|
|
|
|
|
+ binding = _resolve(kwargs.get('session_id', ''))
|
|
|
|
|
+ context = getattr(binding.agent, '_yaoyao_bot_context', None)
|
|
|
|
|
+ if context is None:
|
|
|
|
|
+ raise BridgeError('此会话没有可回查的工具历史', 404, 'history_missing')
|
|
|
|
|
+ try:
|
|
|
|
|
+ return json.dumps(context.read(args.get('ref'), args.get('offset', 0), args.get('limit', 6000)), ensure_ascii=False)
|
|
|
|
|
+ except (ValueError, FileNotFoundError):
|
|
|
|
|
+ raise BridgeError('历史引用或读取范围无效', 404, 'history_missing') from None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def _refresh_agent_tools(agent, binding):
|
|
def _refresh_agent_tools(agent, binding):
|
|
|
# Dashboard /bind runs outside the session's Profile context. Hermes keys
|
|
# Dashboard /bind runs outside the session's Profile context. Hermes keys
|
|
|
# plugin registries and tool-definition caches by the active home; without
|
|
# plugin registries and tool-definition caches by the active home; without
|
|
@@ -405,6 +459,7 @@ def capabilities(profile: str = "default") -> dict:
|
|
|
"computer_runtime_version": 2, "execution_policy_version": 1, "plugin_version": PLUGIN_VERSION, "plugin_fingerprint": PLUGIN_FINGERPRINT,
|
|
"computer_runtime_version": 2, "execution_policy_version": 1, "plugin_version": PLUGIN_VERSION, "plugin_fingerprint": PLUGIN_FINGERPRINT,
|
|
|
"memory_isolation": True, "memory_isolation_transport": "bridge-bind-v1", "memory_extraction": True,
|
|
"memory_isolation": True, "memory_isolation_transport": "bridge-bind-v1", "memory_extraction": True,
|
|
|
"isolated_context_files": True, "skill_learning": True, "model_settings_version": 1, "file_transfer_version": 1,
|
|
"isolated_context_files": True, "skill_learning": True, "model_settings_version": 1, "file_transfer_version": 1,
|
|
|
|
|
+ "context_projection_version": 1,
|
|
|
"runtime_environment": _runtime_environment(profile, definitions)}
|
|
"runtime_environment": _runtime_environment(profile, definitions)}
|
|
|
finally:
|
|
finally:
|
|
|
reset_hermes_home_override(context)
|
|
reset_hermes_home_override(context)
|
|
@@ -596,7 +651,8 @@ def bind(owner: tuple[str, str], body: dict, *, wait_seconds: float = 20) -> dic
|
|
|
current.expires_at = max(current.expires_at, expiry)
|
|
current.expires_at = max(current.expires_at, expiry)
|
|
|
current.stored_session_id = stored
|
|
current.stored_session_id = stored
|
|
|
return {"ok": True, "generation": generation, "expires_at": current.expires_at,
|
|
return {"ok": True, "generation": generation, "expires_at": current.expires_at,
|
|
|
- "native_tools": bool(current.toolset), "computer_runtime_version": 2, "execution_policy_version": 1, "workspace_memory": current.workspace_memory}
|
|
|
|
|
|
|
+ "native_tools": bool(current.toolset), "computer_runtime_version": 2, "execution_policy_version": 1, "workspace_memory": current.workspace_memory,
|
|
|
|
|
+ "context_projection_version": 1}
|
|
|
previous = _generations.get(sid)
|
|
previous = _generations.get(sid)
|
|
|
seen = previous[1] if previous and previous[0] is session else set()
|
|
seen = previous[1] if previous and previous[0] is session else set()
|
|
|
if generation in seen:
|
|
if generation in seen:
|
|
@@ -631,7 +687,8 @@ def bind(owner: tuple[str, str], body: dict, *, wait_seconds: float = 20) -> dic
|
|
|
_retire(binding)
|
|
_retire(binding)
|
|
|
raise
|
|
raise
|
|
|
return {"ok": True, "generation": generation, "expires_at": expiry, "native_tools": bool(binding.toolset),
|
|
return {"ok": True, "generation": generation, "expires_at": expiry, "native_tools": bool(binding.toolset),
|
|
|
- "computer_runtime_version": 2, "execution_policy_version": 1, "workspace_memory": binding.workspace_memory}
|
|
|
|
|
|
|
+ "computer_runtime_version": 2, "execution_policy_version": 1, "workspace_memory": binding.workspace_memory,
|
|
|
|
|
+ "context_projection_version": 1}
|
|
|
|
|
|
|
|
|
|
|
|
|
def unbind(owner: tuple[str, str], body: dict) -> dict:
|
|
def unbind(owner: tuple[str, str], body: dict) -> dict:
|