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:
+32
-30
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user