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:
operator
2026-10-05 20:18:47 +00:00
parent 59c9965791
commit adfcd2e602
38 changed files with 5948 additions and 168 deletions
+32 -30
View File
@@ -49,7 +49,7 @@ import sys
import tempfile
import threading
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
import ssl
# ---------------------------------------------------------------- config
@@ -139,33 +139,34 @@ def check_token(token):
def check_nonce(nonce):
now = time.time()
fresh = []
try:
with open(NONCE_FILE) as f:
for line in f:
parts = line.split()
if len(parts) != 2:
continue
n, t = parts
try:
if now - float(t) < 2 * SIG_MAX_SKEW:
fresh.append((n, t))
except ValueError:
pass
except FileNotFoundError:
pass
if any(n == nonce for n, _ in fresh):
return False
fresh.append((nonce, str(now)))
try:
with open(NONCE_FILE, 'w') as f:
for n, t in fresh:
f.write(f'{n} {t}\n')
os.chmod(NONCE_FILE, 0o600)
except OSError:
return False
return True
with _nonce_lock:
now = time.time()
fresh = []
try:
with open(NONCE_FILE) as f:
for line in f:
parts = line.split()
if len(parts) != 2:
continue
n, t = parts
try:
if now - float(t) < 2 * SIG_MAX_SKEW:
fresh.append((n, t))
except ValueError:
pass
except FileNotFoundError:
pass
if any(n == nonce for n, _ in fresh):
return False
fresh.append((nonce, str(now)))
try:
with open(NONCE_FILE, 'w') as f:
for n, t in fresh:
f.write(f'{n} {t}\n')
os.chmod(NONCE_FILE, 0o600)
except OSError:
return False
return True
def check_signature(identity, payload, signature):
@@ -364,7 +365,7 @@ def _dm_read_validate(raw):
def _dm_read_build(a):
return [sys.executable, os.path.join(BIN_DIR, 'dm.py'), 'read',
'--agent', a['agent'], '--target', a['target'],
'--limit', str(a['limit'])]
'--n', str(a['limit'])]
def _job_run_validate(raw):
@@ -1294,6 +1295,7 @@ def permitted(ident, op):
# ------------------------------------------------------- audit log
_audit_lock = threading.Lock()
_nonce_lock = threading.Lock()
def audit(entry):
@@ -1501,7 +1503,7 @@ def main():
BIN_DIR, JOBS_DIR = args.bin_dir, args.jobs_dir
WORK_DIR = args.work_dir
server = HTTPServer((args.host, args.port), Handler)
server = ThreadingHTTPServer((args.host, args.port), Handler)
cert_file = args.cert_file
key_file = args.key_file