feat(box): passkey fetch, agent key-approval flow, unified lookups, tmux agent UX
- box passkey [show|fetch] (+ muse passkey): documents VM-only passkey (/srv/box/passkey.txt, fallback /etc/netvm/passkey.txt on 34.139.37.135), probes VM over SSH with graceful fallback; --json supported. No secrets on bl. - approvals: request_key_approval / check_node_key_request; KEY_APPROVAL status surfaced in `box approvals check`; allow/deny resolve + audit to box-ctl.jsonl; never auto-approved. New `box approvals request-key <node> --reason`. - box lookup (summary|fleet|threads|unread|approvals|key|docs) and docs-lookup engine with lookup_internal/ database (docs_internal symlink). - muse-tmux: non-TTY attach falls back to scrollback capture; prune NameError fix. - box/muse passthrough for tmux/muse/docs; thread list/view alias + prefix resolve. - Docs: AGENTS.md, AGENT-TOOLING.md, BOX-WEB-SURFACE-GUIDE.md, README. - Tests: key-approval + passkey tests; sync stale sidechat UUIDs and manifest name. - .gitignore runtime trackers (subagent-sessions, conversation-nudge-tracker).
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env python3
|
||||
"""lookup_engine.py — Shared Zero-Downtime Hot-Reloading Pattern & Schema Engine.
|
||||
|
||||
Provides authoritative runtime access to lookup_internal/ databases for
|
||||
daemons (response-harvester, self_main_loop, job-dispatch), CLI commands,
|
||||
and agents.
|
||||
|
||||
Features:
|
||||
- Dynamic mtime-based zero-downtime hot reloading of compiled regex patterns.
|
||||
- Pre-flight soft validation of outbound agent sentences and work orders.
|
||||
- Direct helper functions for core protocol regexes (RESULT, VERB, TOOL, etc.).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
NETVM_ROOT = Path(__file__).resolve().parent.parent
|
||||
LOOKUP_INTERNAL = NETVM_ROOT / "lookup_internal"
|
||||
if not LOOKUP_INTERNAL.exists() and (NETVM_ROOT / "docs_internal").exists():
|
||||
LOOKUP_INTERNAL = NETVM_ROOT / "docs_internal"
|
||||
|
||||
# In-memory cache structures with modification timestamps
|
||||
_CACHE_MTIMES: Dict[str, float] = {}
|
||||
_RAW_CACHE: Dict[str, Any] = {}
|
||||
_COMPILED_PATTERNS: Dict[str, re.Pattern] = {}
|
||||
|
||||
# Fallback hardcoded regexes in case files are missing or unreadable
|
||||
_FALLBACK_RESULT_RE = re.compile(r"\[RESULT\s+([A-Za-z0-9_/-]+)\]\s*(.*?)(?=\[RESULT\s|\Z)", re.S)
|
||||
_FALLBACK_VERB_RE = re.compile(r"\[(ACK|CLAIM|RESULT|DECLINE|NO-ACTION)\s+([A-Za-z0-9_/-]+)\]")
|
||||
_FALLBACK_TOOL_RE = re.compile(r"\[(TOOL|EXEC)\s+([a-zA-Z0-9_.-]+)\s+(\{.*?\})\]", re.S)
|
||||
_FALLBACK_CONTRACT_FOOTER = (
|
||||
"Reply: [ACK id] seen | [CLAIM id] mine | "
|
||||
"[RESULT id] done | [DECLINE id] | [NO-ACTION id]."
|
||||
)
|
||||
|
||||
|
||||
def load_lookup_json(filename: str) -> Dict[str, Any]:
|
||||
"""Load JSON from lookup_internal/ with mtime-based caching."""
|
||||
target_path = LOOKUP_INTERNAL / filename
|
||||
if not target_path.is_file():
|
||||
return {}
|
||||
|
||||
try:
|
||||
current_mtime = os.path.getmtime(target_path)
|
||||
except OSError:
|
||||
return _RAW_CACHE.get(filename, {})
|
||||
|
||||
if filename in _RAW_CACHE and _CACHE_MTIMES.get(filename) == current_mtime:
|
||||
return _RAW_CACHE[filename]
|
||||
|
||||
try:
|
||||
with open(target_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
_RAW_CACHE[filename] = data
|
||||
_CACHE_MTIMES[filename] = current_mtime
|
||||
return data
|
||||
except Exception as e:
|
||||
print(f"[lookup_engine] Warning: Error reading {target_path}: {e}", file=sys.stderr)
|
||||
return _RAW_CACHE.get(filename, {})
|
||||
|
||||
|
||||
def _refresh_compiled_patterns_if_needed():
|
||||
"""Checks regex_patterns.json mtime and recompiles if changed."""
|
||||
global _COMPILED_PATTERNS
|
||||
data = load_lookup_json("regex_patterns.json")
|
||||
patterns_data = data.get("patterns", {})
|
||||
|
||||
target_path = LOOKUP_INTERNAL / "regex_patterns.json"
|
||||
current_mtime = _CACHE_MTIMES.get("regex_patterns.json", 0.0)
|
||||
compiled_mtime = _CACHE_MTIMES.get("_compiled_patterns_mtime", 0.0)
|
||||
|
||||
if current_mtime == compiled_mtime and _COMPILED_PATTERNS:
|
||||
return
|
||||
|
||||
new_compiled = {}
|
||||
for key, entry in patterns_data.items():
|
||||
raw_pat = entry.get("pattern", "")
|
||||
flag_names = entry.get("flags", [])
|
||||
flags = 0
|
||||
for fn in flag_names:
|
||||
if hasattr(re, fn):
|
||||
flags |= getattr(re, fn)
|
||||
try:
|
||||
new_compiled[key] = re.compile(raw_pat, flags)
|
||||
except Exception as e:
|
||||
print(f"[lookup_engine] Warning: Failed to compile pattern '{key}': {e}", file=sys.stderr)
|
||||
|
||||
_COMPILED_PATTERNS = new_compiled
|
||||
_CACHE_MTIMES["_compiled_patterns_mtime"] = current_mtime
|
||||
|
||||
|
||||
def get_compiled_pattern(name: str) -> Optional[re.Pattern]:
|
||||
"""Retrieve a compiled pattern by name, hot-reloading if the database was modified."""
|
||||
_refresh_compiled_patterns_if_needed()
|
||||
return _COMPILED_PATTERNS.get(name)
|
||||
|
||||
|
||||
def get_all_compiled_patterns() -> Dict[str, re.Pattern]:
|
||||
"""Retrieve all compiled patterns with automatic hot-reloading."""
|
||||
_refresh_compiled_patterns_if_needed()
|
||||
return dict(_COMPILED_PATTERNS)
|
||||
|
||||
|
||||
def get_result_regex() -> re.Pattern:
|
||||
"""Return the canonical [RESULT ...] regex."""
|
||||
p = get_compiled_pattern("result")
|
||||
return p if p is not None else _FALLBACK_RESULT_RE
|
||||
|
||||
|
||||
def get_verb_regex() -> re.Pattern:
|
||||
"""Return the canonical [VERB ...] regex (ACK|CLAIM|RESULT|DECLINE|NO-ACTION)."""
|
||||
p = get_compiled_pattern("verb")
|
||||
return p if p is not None else _FALLBACK_VERB_RE
|
||||
|
||||
|
||||
def get_tool_regex() -> re.Pattern:
|
||||
"""Return the canonical [TOOL ...] regex."""
|
||||
p = get_compiled_pattern("tool_call")
|
||||
return p if p is not None else _FALLBACK_TOOL_RE
|
||||
|
||||
|
||||
def get_contract_footer() -> str:
|
||||
"""Return standard contract footer string."""
|
||||
struct_data = load_lookup_json("sentence_structure.json")
|
||||
cf = struct_data.get("structures", {}).get("contract_footer", {})
|
||||
return cf.get("example") or _FALLBACK_CONTRACT_FOOTER
|
||||
|
||||
|
||||
def parse_agent_utterance(text: str) -> List[Dict[str, Any]]:
|
||||
"""Pass text through all registered patterns and extract matched tokens."""
|
||||
_refresh_compiled_patterns_if_needed()
|
||||
patterns_data = load_lookup_json("regex_patterns.json").get("patterns", {})
|
||||
matches = []
|
||||
|
||||
for key, compiled in _COMPILED_PATTERNS.items():
|
||||
m = compiled.search(text)
|
||||
if m:
|
||||
entry = patterns_data.get(key, {})
|
||||
matches.append({
|
||||
"pattern_key": key,
|
||||
"pattern_name": entry.get("name", key),
|
||||
"matched_text": m.group(0),
|
||||
"named_groups": m.groupdict(),
|
||||
"span": m.span()
|
||||
})
|
||||
return matches
|
||||
|
||||
|
||||
def validate_outbound_sentence(text: str) -> Tuple[bool, Optional[str], Optional[str]]:
|
||||
"""Pre-flight check for outbound messages sent via CLI.
|
||||
|
||||
Returns:
|
||||
(is_valid, matched_kind, warning_or_hint)
|
||||
"""
|
||||
cleaned = text.strip()
|
||||
# If message starts with bracketed protocol marker
|
||||
if cleaned.startswith("["):
|
||||
marker = cleaned.split("]")[0] + "]"
|
||||
upper_marker = marker.upper()
|
||||
|
||||
if upper_marker.startswith("[WO:") or upper_marker.startswith("[WORKORDER:"):
|
||||
wo_pat = get_compiled_pattern("work_order")
|
||||
if wo_pat and not wo_pat.search(cleaned):
|
||||
hint = (
|
||||
"Notice: Message starts with a Work Order marker but does not match canonical structure.\n"
|
||||
" Expected format: [WO:<id>] [from <sender>] <title> — <body>\n"
|
||||
" Example: [WO:7fce46e0] [from super] Audit endpoints — Check GET /api/stats\n"
|
||||
" Hint: Query 'box lookup sentence work_order' for full spec."
|
||||
)
|
||||
return False, "work_order", hint
|
||||
return True, "work_order", None
|
||||
|
||||
if upper_marker.startswith("[ACK:") or upper_marker.startswith("[ACK "):
|
||||
ack_pat = get_compiled_pattern("ack")
|
||||
verb_pat = get_compiled_pattern("verb")
|
||||
if (ack_pat and not ack_pat.search(cleaned)) and (verb_pat and not verb_pat.search(cleaned)):
|
||||
hint = (
|
||||
"Notice: Message looks like an ACK but deviates from standard syntax.\n"
|
||||
" Expected format: [ACK:<id>] [from <sender>] or [ACK <id>]\n"
|
||||
" Example: [ACK:7fce46e0] [from 646]"
|
||||
)
|
||||
return False, "ack", hint
|
||||
return True, "ack", None
|
||||
|
||||
if upper_marker.startswith("[RESULT"):
|
||||
res_pat = get_result_regex()
|
||||
if not res_pat.search(cleaned):
|
||||
hint = (
|
||||
"Notice: Message starts with [RESULT] but deviates from standard syntax.\n"
|
||||
" Expected format: [RESULT <job_id>] <status> <summary>\n"
|
||||
" Example: [RESULT 7fce46e0] OK Task completed successfully"
|
||||
)
|
||||
return False, "result", hint
|
||||
return True, "result", None
|
||||
|
||||
return True, "plain_message", None
|
||||
Reference in New Issue
Block a user